Files

213 lines
6.4 KiB
Swift

import Foundation
import Combine
import AppKit
@MainActor
final class TimerStore: ObservableObject {
@Published var timers: [TimerItem] = [] {
didSet {
save()
}
}
private var ticker: AnyCancellable?
private let storageKey = "multiTimerApp.timers.v2"
init() {
load()
if timers.isEmpty {
timers = (1...10).map {
TimerItem(name: "Timer \($0)", durationSeconds: 10 * 60)
}
}
// Beim Start prüfen: Falls Timer vor App-Ende lief, endDate verwenden.
reconcileRunningTimers()
ticker = Timer
.publish(every: 0.5, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
Task { @MainActor in
self?.tick()
}
}
NotificationCenter.default.addObserver(
forName: NSWorkspace.didWakeNotification,
object: nil,
queue: .main
) { [weak self] _ in
Task { @MainActor in
self?.tick()
}
}
}
func addTimer() {
guard timers.count < 15 else { return }
timers.append(
TimerItem(
name: "Timer \(timers.count + 1)",
durationSeconds: 10 * 60
)
)
}
func removeTimer(_ timer: TimerItem) {
AlarmWindowManager.shared.closeWindow(timerID: timer.id)
timers.removeAll { $0.id == timer.id }
}
func start(_ timer: TimerItem) {
guard let index = timers.firstIndex(where: { $0.id == timer.id }) else { return }
if timers[index].remainingSeconds <= 0 {
timers[index].remainingSeconds = timers[index].durationSeconds
}
timers[index].endDate = Date().addingTimeInterval(TimeInterval(timers[index].remainingSeconds))
timers[index].isRunning = true
}
func pause(_ timer: TimerItem) {
guard let index = timers.firstIndex(where: { $0.id == timer.id }) else { return }
if let endDate = timers[index].endDate {
timers[index].remainingSeconds = max(0, Int(ceil(endDate.timeIntervalSinceNow)))
}
timers[index].isRunning = false
timers[index].endDate = nil
}
func reset(_ timer: TimerItem) {
guard let index = timers.firstIndex(where: { $0.id == timer.id }) else { return }
timers[index].isRunning = false
timers[index].endDate = nil
timers[index].remainingSeconds = timers[index].durationSeconds
AlarmWindowManager.shared.closeWindow(timerID: timer.id)
}
func repeatTimer(timerID: UUID) {
guard let index = timers.firstIndex(where: { $0.id == timerID }) else { return }
timers[index].remainingSeconds = timers[index].durationSeconds
timers[index].endDate = Date().addingTimeInterval(TimeInterval(timers[index].durationSeconds))
timers[index].isRunning = true
}
func finishTimer(timerID: UUID) {
guard let index = timers.firstIndex(where: { $0.id == timerID }) else { return }
timers[index].isRunning = false
timers[index].endDate = nil
timers[index].remainingSeconds = timers[index].durationSeconds
}
func durationChanged(for timerID: UUID, newDurationSeconds: Int) {
guard let index = timers.firstIndex(where: { $0.id == timerID }) else { return }
let safeSeconds = max(60, min(24 * 3600, newDurationSeconds))
timers[index].durationSeconds = safeSeconds
if !timers[index].isRunning {
timers[index].remainingSeconds = safeSeconds
timers[index].endDate = nil
}
}
private func tick() {
var changed = false
for index in timers.indices where timers[index].isRunning {
guard let endDate = timers[index].endDate else {
timers[index].endDate = Date().addingTimeInterval(TimeInterval(timers[index].remainingSeconds))
changed = true
continue
}
let secondsLeft = max(0, Int(ceil(endDate.timeIntervalSinceNow)))
if timers[index].remainingSeconds != secondsLeft {
timers[index].remainingSeconds = secondsLeft
changed = true
}
if secondsLeft <= 0 {
handleExpiredTimer(at: index)
changed = true
}
}
if changed {
objectWillChange.send()
save()
}
}
private func handleExpiredTimer(at index: Int) {
timers[index].remainingSeconds = 0
timers[index].isRunning = false
timers[index].endDate = nil
timers[index].completedCount += 1
AlarmManager.shared.playAlarm()
let timerID = timers[index].id
let timerName = timers[index].name
if timers[index].autoRepeat {
timers[index].remainingSeconds = timers[index].durationSeconds
timers[index].endDate = Date().addingTimeInterval(TimeInterval(timers[index].durationSeconds))
timers[index].isRunning = true
} else {
AlarmWindowManager.shared.showAlarm(
timerID: timerID,
timerName: timerName,
onRepeat: { [weak self] in
self?.repeatTimer(timerID: timerID)
},
onFinish: { [weak self] in
self?.finishTimer(timerID: timerID)
}
)
}
}
private func reconcileRunningTimers() {
for index in timers.indices where timers[index].isRunning {
if let endDate = timers[index].endDate {
timers[index].remainingSeconds = max(0, Int(ceil(endDate.timeIntervalSinceNow)))
} else {
timers[index].endDate = Date().addingTimeInterval(TimeInterval(timers[index].remainingSeconds))
}
if timers[index].remainingSeconds <= 0 {
timers[index].isRunning = false
timers[index].remainingSeconds = 0
}
}
}
private func save() {
guard let data = try? JSONEncoder().encode(timers) else { return }
UserDefaults.standard.set(data, forKey: storageKey)
}
private func load() {
guard
let data = UserDefaults.standard.data(forKey: storageKey),
let decoded = try? JSONDecoder().decode([TimerItem].self, from: data)
else {
return
}
timers = decoded
}
}