Skip to content
Open
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
23 changes: 23 additions & 0 deletions app/main/lib/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
12 changes: 8 additions & 4 deletions app/renderer/components/break.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Settings | null>(null);
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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}
/>
)}
</div>
Expand Down
20 changes: 13 additions & 7 deletions app/renderer/components/break/break-notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,6 +16,8 @@ interface BreakNotificationProps {
timeSinceLastBreak: number | null;
textColor: string;
backgroundColor: string;
automaticallyStartBreaksDelaySeconds: number;
automaticallyStartBreaks: boolean;
}

export function BreakNotification({
Expand All @@ -29,10 +30,15 @@ export function BreakNotification({
timeSinceLastBreak,
textColor,
backgroundColor,
automaticallyStartBreaksDelaySeconds,
automaticallyStartBreaks,
}: BreakNotificationProps) {
const [phase, setPhase] = useState<"grace" | "countdown">("grace");
const [msRemaining, setMsRemaining] = useState<number>(0);

const totalCountdownMs = automaticallyStartBreaksDelaySeconds * 1000;
const gracePeriodMs = Math.min(MAX_GRACE_PERIOD_MS, totalCountdownMs);

useEffect(() => {
const startTime = moment();
let timeoutId: NodeJS.Timeout;
Expand All @@ -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;
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions app/renderer/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export default function SettingsEl() {
secondsField = "postponeLengthSeconds";
} else if (fieldName === "idleResetLength") {
secondsField = "idleResetLengthSeconds";
} else if (fieldName === "automaticallyStartBreaksDelaySeconds") {
secondsField = "automaticallyStartBreaksDelaySeconds";
} else {
return;
}
Expand Down Expand Up @@ -161,6 +163,7 @@ export default function SettingsEl() {
<AdvancedCard
settingsDraft={settingsDraft}
onSwitchChange={handleSwitchChange}
onDateChange={handleDateChange}
/>
</TabsContent>

Expand Down
27 changes: 24 additions & 3 deletions app/renderer/components/settings/advanced-card.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<SettingsCard title="Advanced">
<div className="space-y-4">
<div className="flex items-center space-x-2">
<Switch
checked={settingsDraft.immediatelyStartBreaks}
checked={settingsDraft.automaticallyStartBreaks}
onCheckedChange={(checked) =>
onSwitchChange("immediatelyStartBreaks", checked)
onSwitchChange("automaticallyStartBreaks", checked)
}
disabled={settingsDraft.notificationType !== NotificationType.Popup}
/>
<Label>Immediately start breaks</Label>
<Label>Automatically start breaks</Label>
</div>

{settingsDraft.automaticallyStartBreaks && (
<div className="pl-6 space-y-2">
<Label className="text-sm font-medium">Delay before automatic start</Label>
<TimeInput
precision="seconds"
value={settingsDraft.automaticallyStartBreaksDelaySeconds}
onChange={(seconds) => {
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}
/>
</div>
)}

<div className="flex items-center space-x-2">
<Switch
checked={settingsDraft.endBreakEnabled}
Expand Down
9 changes: 9 additions & 0 deletions app/renderer/components/settings/settings-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Settings } from "../../../types/settings";

/**
* Determines if breaks should start immediately without any countdown delay.
* Returns true when automatic break start is enabled with zero delay.
*/
export function shouldStartBreakImmediately(settings: Settings): boolean {
return settings.automaticallyStartBreaks && settings.automaticallyStartBreaksDelaySeconds === 0;
}
7 changes: 5 additions & 2 deletions app/renderer/components/settings/skip-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import SettingsCard from "./settings-card";
import { Settings } from "../../../types/settings";
import { shouldStartBreakImmediately } from "./settings-utils";

interface SkipCardProps {
settingsDraft: Settings;
Expand All @@ -10,17 +11,19 @@ export default function SkipCard({
settingsDraft,
onSwitchChange,
}: SkipCardProps) {
const immediatelyStartBreaks = shouldStartBreakImmediately(settingsDraft);

return (
<SettingsCard
title="Skip"
helperText="Allow skipping breaks entirely without rescheduling them."
toggle={{
checked:
settingsDraft.skipBreakEnabled &&
!settingsDraft.immediatelyStartBreaks,
!immediatelyStartBreaks,
onCheckedChange: (checked) =>
onSwitchChange("skipBreakEnabled", checked),
disabled: settingsDraft.immediatelyStartBreaks,
disabled: immediatelyStartBreaks,
}}
/>
);
Expand Down
11 changes: 7 additions & 4 deletions app/renderer/components/settings/snooze-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,17 +24,19 @@ export default function SnoozeCard({
onDateChange,
onPostponeLimitChange,
}: SnoozeCardProps) {
const immediatelyStartBreaks = shouldStartBreakImmediately(settingsDraft);

return (
<SettingsCard
title="Snooze"
helperText="Snoozing allows you to postpone breaks when busy."
toggle={{
checked:
settingsDraft.postponeBreakEnabled &&
!settingsDraft.immediatelyStartBreaks,
!immediatelyStartBreaks,
onCheckedChange: (checked) =>
onSwitchChange("postponeBreakEnabled", checked),
disabled: settingsDraft.immediatelyStartBreaks,
disabled: immediatelyStartBreaks,
}}
>
<div className="grid grid-cols-2 gap-4">
Expand All @@ -51,7 +54,7 @@ export default function SnoozeCard({
}}
disabled={
!settingsDraft.postponeBreakEnabled ||
settingsDraft.immediatelyStartBreaks
immediatelyStartBreaks
}
/>
</div>
Expand All @@ -62,7 +65,7 @@ export default function SnoozeCard({
onValueChange={onPostponeLimitChange}
disabled={
!settingsDraft.postponeBreakEnabled ||
settingsDraft.immediatelyStartBreaks
immediatelyStartBreaks
}
>
<SelectTrigger>
Expand Down
6 changes: 4 additions & 2 deletions app/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export interface Settings {
endBreakEnabled: boolean;
skipBreakEnabled: boolean;
postponeBreakEnabled: boolean;
immediatelyStartBreaks: boolean;
automaticallyStartBreaks: boolean;
automaticallyStartBreaksDelaySeconds: number;
}

export const defaultWorkingRange: WorkingHoursRange = {
Expand Down Expand Up @@ -111,7 +112,8 @@ export const defaultSettings: Settings = {
endBreakEnabled: true,
skipBreakEnabled: false,
postponeBreakEnabled: true,
immediatelyStartBreaks: false,
automaticallyStartBreaks: true,
automaticallyStartBreaksDelaySeconds: 120,
};

export interface DayConfig {
Expand Down