pommedoro/Sources/Pommedoro/CountdownPulseSchedule.swift

49 lines
2.2 KiB
Swift

import Foundation
/// Single source of truth for countdown pulse timing. Used by both default and debug modes
/// First 30s: blink every 10s; next 15s: every 5s; next 10s: every 2s; last 5s: solid.
enum CountdownPulseSchedule {
/// Whether we are in the "flashing" phase (remaining within countdown window).
static func isInCountdownPhase(remainingSeconds: Int, countdownDuration: Int, workTimerDuration: Int) -> Bool {
let effective = min(countdownDuration, workTimerDuration)
return remainingSeconds <= effective && remainingSeconds > 0
}
/// Whether to trigger a blink this second. Same logic for debug and default.
/// First 30s remaining: every 10s; next 15s: every 5s; next 10s: every 2s; last 5s: solid.
static func shouldBlink(remainingSeconds: Int) -> Bool {
let s = remainingSeconds
if s <= 5 { return false } // last 5: solid
if s <= 15 { return s % 2 == 0 } // next 10: every 2s (14,12,10,8,6)
if s <= 30 { return s % 5 == 0 } // next 15: every 5s (30,25,20,15)
return s % 10 == 0 // first 30: every 10s (60,50,40,30,)
}
/// When remaining <= 5, show solid overlay instead of blinking.
static func shouldBeSolid(remainingSeconds: Int) -> Bool {
return remainingSeconds > 0 && remainingSeconds <= 5
}
/// Blink animation durations (seconds). Single place so overlay timing matches schedule.
struct BlinkDurations {
var fadeIn: Double
var hold: Double
var fadeOut: Double
}
static func blinkDurations(remainingSeconds: Int, durationScale: Double) -> BlinkDurations {
let scale = durationScale
if remainingSeconds > 60 {
return BlinkDurations(fadeIn: 1.0 * scale, hold: 0.8 * scale, fadeOut: 1.0 * scale)
}
if remainingSeconds > 30 {
return BlinkDurations(fadeIn: 0.8 * scale, hold: 0.6 * scale, fadeOut: 0.8 * scale)
}
if remainingSeconds > 10 {
return BlinkDurations(fadeIn: 0.5 * scale, hold: 0.4 * scale, fadeOut: 0.6 * scale)
}
return BlinkDurations(fadeIn: 0.3 * scale, hold: 0.3 * scale, fadeOut: 0.4 * scale)
}
}