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 } }