diff --git a/app/main/lib/store.ts b/app/main/lib/store.ts index 16fe5e8f..eae95af7 100644 --- a/app/main/lib/store.ts +++ b/app/main/lib/store.ts @@ -99,6 +99,29 @@ const migrations: Migration[] = [ return settings; }, }, + { + version: 3, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + migrate: (settings: any) => { + // immediatelyStartBreaks to automaticallyStartBreaks migration + if (settings.immediatelyStartBreaks !== undefined) { + console.log("Migrating immediatelyStartBreaks to automaticallyStartBreaks"); + + if (settings.immediatelyStartBreaks) { + // User wanted immediate start - enable auto start with 0 delay + settings.automaticallyStartBreaks = true; + settings.automaticallyStartBreaksDelaySeconds = 0; + } else { + // else legacy behaviour was to start breaks automatically after 120 seconds (HARD CODED) + settings.automaticallyStartBreaks = true; + settings.automaticallyStartBreaksDelaySeconds = 120; // default value + } + + delete settings.immediatelyStartBreaks; + } + return settings; + }, + }, ]; const store = new Store({ diff --git a/app/renderer/components/break.tsx b/app/renderer/components/break.tsx index 0732d0c8..cbbf74b5 100644 --- a/app/renderer/components/break.tsx +++ b/app/renderer/components/break.tsx @@ -4,6 +4,7 @@ import { Settings, SoundType } from "../../types/settings"; import { BreakNotification } from "./break/break-notification"; import { BreakProgress } from "./break/break-progress"; import { createDarkerRgba } from "./break/utils"; +import { shouldStartBreakImmediately } from "./settings/settings-utils"; export default function Break() { const [settings, setSettings] = useState(null); @@ -32,8 +33,8 @@ export default function Break() { setSettings(settings); setTimeSinceLastBreak(timeSince); - // Skip the countdown if immediately start breaks is enabled or started from tray - if (settings.immediatelyStartBreaks || startedFromTray) { + // Skip the countdown if auto start delay is 0 or started from tray + if (shouldStartBreakImmediately(settings) || startedFromTray) { setCountingDown(false); } @@ -130,14 +131,17 @@ export default function Break() { postponeBreakEnabled={ settings.postponeBreakEnabled && allowPostpone && - !settings.immediatelyStartBreaks + !shouldStartBreakImmediately(settings) } skipBreakEnabled={ - settings.skipBreakEnabled && !settings.immediatelyStartBreaks + settings.skipBreakEnabled && + !shouldStartBreakImmediately(settings) } timeSinceLastBreak={timeSinceLastBreak} textColor={settings.textColor} backgroundColor={settings.backgroundColor} + automaticallyStartBreaksDelaySeconds={settings.automaticallyStartBreaksDelaySeconds} + automaticallyStartBreaks={settings.automaticallyStartBreaks} /> )} diff --git a/app/renderer/components/break/break-notification.tsx b/app/renderer/components/break/break-notification.tsx index c122f659..577fa264 100644 --- a/app/renderer/components/break/break-notification.tsx +++ b/app/renderer/components/break/break-notification.tsx @@ -4,8 +4,7 @@ import moment from "moment"; import { useEffect, useState } from "react"; import { formatTimeSinceLastBreak } from "./utils"; -const GRACE_PERIOD_MS = 60000; -const TOTAL_COUNTDOWN_MS = 120000; +const MAX_GRACE_PERIOD_MS = 60000; interface BreakNotificationProps { onCountdownOver: () => void; @@ -17,6 +16,8 @@ interface BreakNotificationProps { timeSinceLastBreak: number | null; textColor: string; backgroundColor: string; + automaticallyStartBreaksDelaySeconds: number; + automaticallyStartBreaks: boolean; } export function BreakNotification({ @@ -29,10 +30,15 @@ export function BreakNotification({ timeSinceLastBreak, textColor, backgroundColor, + automaticallyStartBreaksDelaySeconds, + automaticallyStartBreaks, }: BreakNotificationProps) { const [phase, setPhase] = useState<"grace" | "countdown">("grace"); const [msRemaining, setMsRemaining] = useState(0); + const totalCountdownMs = automaticallyStartBreaksDelaySeconds * 1000; + const gracePeriodMs = Math.min(MAX_GRACE_PERIOD_MS, totalCountdownMs); + useEffect(() => { const startTime = moment(); let timeoutId: NodeJS.Timeout; @@ -41,11 +47,11 @@ export function BreakNotification({ const now = moment(); const elapsedMs = now.diff(startTime, "milliseconds"); - if (elapsedMs < GRACE_PERIOD_MS) { + if (elapsedMs < gracePeriodMs || !automaticallyStartBreaks) { setPhase("grace"); - } else if (elapsedMs < TOTAL_COUNTDOWN_MS) { + } else if (elapsedMs < totalCountdownMs) { setPhase("countdown"); - setMsRemaining(TOTAL_COUNTDOWN_MS - elapsedMs); + setMsRemaining(totalCountdownMs - elapsedMs); } else { onCountdownOver(); return; @@ -61,10 +67,10 @@ export function BreakNotification({ clearTimeout(timeoutId); } }; - }, [onCountdownOver]); + }, [onCountdownOver, automaticallyStartBreaks, totalCountdownMs, gracePeriodMs]); const secondsRemaining = Math.ceil(msRemaining / 1000); - const countdownDurationMs = TOTAL_COUNTDOWN_MS - GRACE_PERIOD_MS; + const countdownDurationMs = totalCountdownMs - gracePeriodMs; const progressValue = phase === "countdown" ? ((countdownDurationMs - msRemaining) / countdownDurationMs) * 100 diff --git a/app/renderer/components/settings.tsx b/app/renderer/components/settings.tsx index 5db52d45..2468e24a 100644 --- a/app/renderer/components/settings.tsx +++ b/app/renderer/components/settings.tsx @@ -59,6 +59,8 @@ export default function SettingsEl() { secondsField = "postponeLengthSeconds"; } else if (fieldName === "idleResetLength") { secondsField = "idleResetLengthSeconds"; + } else if (fieldName === "automaticallyStartBreaksDelaySeconds") { + secondsField = "automaticallyStartBreaksDelaySeconds"; } else { return; } @@ -161,6 +163,7 @@ export default function SettingsEl() { diff --git a/app/renderer/components/settings/advanced-card.tsx b/app/renderer/components/settings/advanced-card.tsx index 2eb2e1ef..f115386b 100644 --- a/app/renderer/components/settings/advanced-card.tsx +++ b/app/renderer/components/settings/advanced-card.tsx @@ -1,31 +1,52 @@ import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import SettingsCard from "./settings-card"; +import TimeInput from "./time-input"; import { NotificationType, Settings } from "../../../types/settings"; interface AdvancedCardProps { settingsDraft: Settings; onSwitchChange: (field: string, checked: boolean) => void; + onDateChange: (fieldName: string, newVal: Date) => void; } export default function AdvancedCard({ settingsDraft, onSwitchChange, + onDateChange, }: AdvancedCardProps) { return (
- onSwitchChange("immediatelyStartBreaks", checked) + onSwitchChange("automaticallyStartBreaks", checked) } disabled={settingsDraft.notificationType !== NotificationType.Popup} /> - +
+ {settingsDraft.automaticallyStartBreaks && ( +
+ + { + const date = new Date(); + date.setHours(Math.floor(seconds / 3600)); + date.setMinutes(Math.floor((seconds % 3600) / 60)); + date.setSeconds(seconds % 60); + onDateChange("automaticallyStartBreaksDelaySeconds", date); + }} + disabled={settingsDraft.notificationType !== NotificationType.Popup} + /> +
+ )} +
onSwitchChange("skipBreakEnabled", checked), - disabled: settingsDraft.immediatelyStartBreaks, + disabled: immediatelyStartBreaks, }} /> ); diff --git a/app/renderer/components/settings/snooze-card.tsx b/app/renderer/components/settings/snooze-card.tsx index 8a5af3c4..4ed644bd 100644 --- a/app/renderer/components/settings/snooze-card.tsx +++ b/app/renderer/components/settings/snooze-card.tsx @@ -9,6 +9,7 @@ import { import SettingsCard from "./settings-card"; import TimeInput from "./time-input"; import { Settings } from "../../../types/settings"; +import { shouldStartBreakImmediately } from "./settings-utils"; interface SnoozeCardProps { settingsDraft: Settings; @@ -23,6 +24,8 @@ export default function SnoozeCard({ onDateChange, onPostponeLimitChange, }: SnoozeCardProps) { + const immediatelyStartBreaks = shouldStartBreakImmediately(settingsDraft); + return ( onSwitchChange("postponeBreakEnabled", checked), - disabled: settingsDraft.immediatelyStartBreaks, + disabled: immediatelyStartBreaks, }} >
@@ -51,7 +54,7 @@ export default function SnoozeCard({ }} disabled={ !settingsDraft.postponeBreakEnabled || - settingsDraft.immediatelyStartBreaks + immediatelyStartBreaks } />
@@ -62,7 +65,7 @@ export default function SnoozeCard({ onValueChange={onPostponeLimitChange} disabled={ !settingsDraft.postponeBreakEnabled || - settingsDraft.immediatelyStartBreaks + immediatelyStartBreaks } > diff --git a/app/types/settings.ts b/app/types/settings.ts index 8e618dce..e6a502e7 100644 --- a/app/types/settings.ts +++ b/app/types/settings.ts @@ -52,7 +52,8 @@ export interface Settings { endBreakEnabled: boolean; skipBreakEnabled: boolean; postponeBreakEnabled: boolean; - immediatelyStartBreaks: boolean; + automaticallyStartBreaks: boolean; + automaticallyStartBreaksDelaySeconds: number; } export const defaultWorkingRange: WorkingHoursRange = { @@ -111,7 +112,8 @@ export const defaultSettings: Settings = { endBreakEnabled: true, skipBreakEnabled: false, postponeBreakEnabled: true, - immediatelyStartBreaks: false, + automaticallyStartBreaks: true, + automaticallyStartBreaksDelaySeconds: 120, }; export interface DayConfig {