Skip to content

Commit 30bb1bf

Browse files
CopilotBruno-366
andcommitted
Add LiSS workout duration timer functionality with start/pause/stop controls
Co-authored-by: Bruno-366 <81762173+Bruno-366@users.noreply.github.com>
1 parent 88a17ca commit 30bb1bf

File tree

6 files changed

+230
-0
lines changed

6 files changed

+230
-0
lines changed

src/App.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@
9797
workoutType: null,
9898
phase: 'initial',
9999
startTime: 0
100+
},
101+
lissTimer: {
102+
isActive: false,
103+
isPaused: false,
104+
timeLeft: 0,
105+
totalTime: 0,
106+
startTime: 0,
107+
pausedTime: 0
100108
}
101109
}))
102110
}

src/components/CardioWorkouts.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import type { Workout, CardioWorkout } from '../types'
3+
import LiSSTimer from './LiSSTimer.svelte'
34
45
interface CardioWorkoutsProps {
56
workout: Workout
@@ -43,6 +44,12 @@
4344
{/if}
4445
{/if}
4546
</div>
47+
48+
{#if workout.type === 'liss' && typeof cardioWorkout().duration === 'number'}
49+
<!-- Include LiSS Timer for liss workouts with numeric duration -->
50+
<LiSSTimer duration={cardioWorkout().duration as number} />
51+
{/if}
52+
4653
<button
4754
onclick={onCompleteWorkout}
4855
class="w-full bg-green-500 hover:bg-green-600 text-white font-semibold py-3 rounded-lg transition-colors mt-4"

src/components/LiSSTimer.svelte

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
<script lang="ts">
2+
import { Timer, Play, Pause, Square } from 'lucide-svelte'
3+
import { showRestCompleteNotification } from '../utils'
4+
import { uiStore } from '../stores'
5+
6+
interface LiSSTimerProps {
7+
duration: number // Duration in minutes
8+
}
9+
10+
let { duration }: LiSSTimerProps = $props()
11+
12+
// Access LiSS timer directly from store
13+
const lissTimer = $derived($uiStore.lissTimer)
14+
15+
// Update store function
16+
const updateLissTimer = (updates: Partial<typeof lissTimer>) => {
17+
uiStore.update(state => ({
18+
...state,
19+
lissTimer: { ...state.lissTimer, ...updates }
20+
}))
21+
}
22+
23+
// Start LiSS timer function
24+
const startLissTimer = () => {
25+
const durationInSeconds = duration * 60
26+
const now = Date.now()
27+
28+
if (lissTimer.isPaused) {
29+
// Resume from pause
30+
updateLissTimer({
31+
isActive: true,
32+
isPaused: false,
33+
startTime: now - (lissTimer.totalTime - lissTimer.timeLeft) * 1000
34+
})
35+
} else {
36+
// Start fresh
37+
updateLissTimer({
38+
isActive: true,
39+
isPaused: false,
40+
timeLeft: durationInSeconds,
41+
totalTime: durationInSeconds,
42+
startTime: now,
43+
pausedTime: 0
44+
})
45+
}
46+
}
47+
48+
// Pause LiSS timer function
49+
const pauseLissTimer = () => {
50+
updateLissTimer({
51+
isActive: false,
52+
isPaused: true,
53+
pausedTime: Date.now()
54+
})
55+
}
56+
57+
// Stop LiSS timer function
58+
const stopLissTimer = () => {
59+
updateLissTimer({
60+
isActive: false,
61+
isPaused: false,
62+
timeLeft: 0,
63+
totalTime: 0,
64+
startTime: 0,
65+
pausedTime: 0
66+
})
67+
}
68+
69+
// Derived progress percentage
70+
const progressPercent = $derived(() => {
71+
if (lissTimer.totalTime === 0) return 0
72+
return ((lissTimer.totalTime - lissTimer.timeLeft) / lissTimer.totalTime) * 100
73+
})
74+
75+
// Timer interval effect - timestamp-based to prevent background throttling
76+
$effect(() => {
77+
let interval: number | undefined
78+
79+
if (lissTimer.isActive && lissTimer.timeLeft > 0) {
80+
interval = setInterval(() => {
81+
const now = Date.now()
82+
const elapsedSeconds = Math.floor((now - lissTimer.startTime) / 1000)
83+
const newTimeLeft = Math.max(0, lissTimer.totalTime - elapsedSeconds)
84+
85+
updateLissTimer({ timeLeft: newTimeLeft })
86+
}, 100) // Check more frequently for smoother updates
87+
}
88+
89+
return () => {
90+
if (interval) clearInterval(interval)
91+
}
92+
})
93+
94+
// Separate effect to handle notification when timer reaches 0
95+
$effect(() => {
96+
if (lissTimer.isActive && lissTimer.timeLeft === 0) {
97+
showRestCompleteNotification()
98+
// Auto-pause when complete
99+
updateLissTimer({ isActive: false, isPaused: false })
100+
}
101+
})
102+
103+
// Derived timer display values
104+
const minutes = $derived(() => Math.floor(lissTimer.timeLeft / 60))
105+
const seconds = $derived(() => lissTimer.timeLeft % 60)
106+
const isTimerActive = $derived(() => lissTimer.isActive || lissTimer.isPaused || lissTimer.timeLeft > 0)
107+
</script>
108+
109+
{#if isTimerActive()}
110+
<div class="mb-4 p-4 bg-white rounded-lg border-2 border-blue-200">
111+
<div class="flex items-center justify-between mb-2">
112+
<div class="flex items-center gap-2">
113+
<Timer class="w-5 h-5 text-blue-600" />
114+
<span class="text-sm font-semibold text-gray-700">
115+
LiSS Timer
116+
</span>
117+
</div>
118+
<div class="text-lg font-bold text-gray-900">
119+
{minutes()}:{seconds().toString().padStart(2, '0')}
120+
</div>
121+
</div>
122+
123+
<div class="mb-3">
124+
<div class="bg-gray-200 h-3 rounded-full overflow-hidden">
125+
<div
126+
class="h-full transition-all duration-300 bg-blue-500"
127+
style="width: {progressPercent()}%"
128+
></div>
129+
</div>
130+
</div>
131+
132+
<div class="flex gap-2">
133+
{#if !lissTimer.isActive && !lissTimer.isPaused}
134+
<!-- Start button - only show when timer is not started -->
135+
<button
136+
onclick={startLissTimer}
137+
class="flex-1 bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition-colors text-sm flex items-center justify-center gap-2"
138+
>
139+
<Play class="w-4 h-4" />
140+
Start
141+
</button>
142+
{:else if lissTimer.isActive}
143+
<!-- Pause button - show when timer is running -->
144+
<button
145+
onclick={pauseLissTimer}
146+
class="flex-1 bg-yellow-500 hover:bg-yellow-600 text-white font-medium py-2 px-4 rounded-lg transition-colors text-sm flex items-center justify-center gap-2"
147+
>
148+
<Pause class="w-4 h-4" />
149+
Pause
150+
</button>
151+
{:else if lissTimer.isPaused}
152+
<!-- Resume button - show when timer is paused -->
153+
<button
154+
onclick={startLissTimer}
155+
class="flex-1 bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-4 rounded-lg transition-colors text-sm flex items-center justify-center gap-2"
156+
>
157+
<Play class="w-4 h-4" />
158+
Resume
159+
</button>
160+
{/if}
161+
162+
<!-- Stop button - always available when timer exists -->
163+
<button
164+
onclick={stopLissTimer}
165+
class="flex-1 bg-red-500 hover:bg-red-600 text-white font-medium py-2 px-4 rounded-lg transition-colors text-sm flex items-center justify-center gap-2"
166+
>
167+
<Square class="w-4 h-4" />
168+
Stop
169+
</button>
170+
</div>
171+
</div>
172+
{:else}
173+
<!-- Start timer button when no timer is active -->
174+
<div class="mb-4">
175+
<button
176+
onclick={startLissTimer}
177+
class="w-full bg-blue-500 hover:bg-blue-600 text-white font-semibold py-3 rounded-lg transition-colors flex items-center justify-center gap-2"
178+
>
179+
<Timer class="w-5 h-5" />
180+
Start {duration} min timer
181+
</button>
182+
</div>
183+
{/if}

src/components/ResetProgress.svelte

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
workoutType: null,
3636
phase: 'initial',
3737
startTime: 0
38+
},
39+
lissTimer: {
40+
isActive: false,
41+
isPaused: false,
42+
timeLeft: 0,
43+
totalTime: 0,
44+
startTime: 0,
45+
pausedTime: 0
3846
}
3947
})
4048

src/stores.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ interface UIState {
1414
phase: 'initial' | 'extended'
1515
startTime: number
1616
}
17+
lissTimer: {
18+
isActive: boolean
19+
isPaused: boolean
20+
timeLeft: number
21+
totalTime: number
22+
startTime: number
23+
pausedTime: number
24+
}
1725
}
1826

1927
interface WorkoutState {
@@ -167,6 +175,14 @@ const defaultUIState: UIState = {
167175
workoutType: null,
168176
phase: 'initial',
169177
startTime: 0
178+
},
179+
lissTimer: {
180+
isActive: false,
181+
isPaused: false,
182+
timeLeft: 0,
183+
totalTime: 0,
184+
startTime: 0,
185+
pausedTime: 0
170186
}
171187
}
172188

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,14 @@ export interface AppState {
6767
phase: 'initial' | 'extended';
6868
startTime: number;
6969
};
70+
lissTimer: {
71+
isActive: boolean;
72+
isPaused: boolean;
73+
timeLeft: number;
74+
totalTime: number;
75+
startTime: number;
76+
pausedTime: number;
77+
};
7078
}
7179

7280
export interface WarmupSet {

0 commit comments

Comments
 (0)