diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index fca41f3..f2c6563 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -166,26 +166,6 @@ class Conductor: ObservableObject { do { let end = try self._scheduleSoundPlayer(countdown: countdown, in: countdown.duration) - -// let start = Date() -// let end = start.addingTimeInterval(countdown.duration) -// FileLogger.log("schedule countdown \(self._timerName(countdownId)) at \(end)") -// -// let sound = countdown.someSound -// let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound) -// -// FileLogger.log("a) self._delayedSoundPlayers count = \(self._delayedSoundPlayers.count)") -// self._delayedSoundPlayers[countdownId] = soundPlayer -// FileLogger.log("b) self._delayedSoundPlayers count = \(self._delayedSoundPlayers.count)") -// -// try soundPlayer.start(in: countdown.duration, -// repeatCount: Int(countdown.repeatCount)) -// -// let dateInterval = DateInterval(start: start, end: end) -// self.currentCountdowns[countdownId] = dateInterval -// -// self._launchLiveActivity(timer: countdown, date: end) - if Preferences.playConfirmationSound { self._playConfirmationSound(timer: countdown) } @@ -306,18 +286,22 @@ class Conductor: ObservableObject { // MARK: - Stopwatch - func startStopwatch(_ stopwatch: Stopwatch) { + func startStopwatch(_ stopwatchId: TimerID) { DispatchQueue.main.async { - let lsw: LiveStopWatch = LiveStopWatch(start: Date()) - self.currentStopwatches[stopwatch.stringId] = lsw + guard let stopWatch = IntentDataProvider.main.timer(id: stopwatchId) as? Stopwatch else { + return + } + + let lsw: LiveStopWatch = LiveStopWatch(start: Date()) + self.currentStopwatches[stopWatch.stringId] = lsw if Preferences.playConfirmationSound { self._playSound(Const.confirmationSound.rawValue) } - self._endLiveActivity(timerId: stopwatch.stringId) - self._launchLiveActivity(timer: stopwatch, date: lsw.start) + self._endLiveActivity(timerId: stopWatch.stringId) + self._launchLiveActivity(timer: stopWatch, date: lsw.start) } } diff --git a/LeCountdown/Intent/StartTimerIntent.swift b/LeCountdown/Intent/StartTimerIntent.swift index 83938ac..3cbc7cd 100644 --- a/LeCountdown/Intent/StartTimerIntent.swift +++ b/LeCountdown/Intent/StartTimerIntent.swift @@ -8,6 +8,7 @@ import Foundation import AppIntents import SwiftUI +import CoreData @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) struct StartTimerIntent: AudioStartingIntent { @@ -31,29 +32,38 @@ struct StartTimerIntent: AudioStartingIntent { if let timer { timerIdentifier = timer } else { - timerIdentifier = try await $timer.requestDisambiguation(among: timerEntities()) + let entities = await _timerEntities() + timerIdentifier = try await $timer.requestDisambiguation(among: entities) } - - if let abstractTimer = IntentDataProvider.main.timer(id: timerIdentifier.id) { + + if let abstractTimer = await _timer(id: timerIdentifier.id) { do { _ = try await TimerRouter.performAction(timer: abstractTimer) return .result(value: 1) } catch { Logger.error(error) -// let dialog: IntentDialog = "\(error.localizedDescription)" throw error } } else { throw AppError.timerNotFound(id: timerIdentifier.id) } + + } + + fileprivate func _timerEntities() async -> [TimerIdentifierAppEntity] { + await PersistenceController.shared.container.performBackgroundTask { context in + do { + let timers: [AbstractTimer] = try IntentDataProvider.main.timers(context: context) + return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } + } catch { + return [] + } + } } - func timerEntities() -> [TimerIdentifierAppEntity] { - do { - let timers: [AbstractTimer] = try IntentDataProvider.main.timers() - return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } - } catch { - return [] + fileprivate func _timer(id: String) async -> AbstractTimer? { + await PersistenceController.shared.container.performBackgroundTask { context in + return IntentDataProvider.main.timer(context: context, id: id) } } diff --git a/LeCountdown/Intent/TimerIdentifierAppEntity.swift b/LeCountdown/Intent/TimerIdentifierAppEntity.swift index 5904081..2aa638c 100644 --- a/LeCountdown/Intent/TimerIdentifierAppEntity.swift +++ b/LeCountdown/Intent/TimerIdentifierAppEntity.swift @@ -10,39 +10,37 @@ import AppIntents @available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) struct TimerIdentifierAppEntity: AppEntity { + static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Timer Identifier") + + static var defaultQuery = TimerIdentifierAppEntityQuery() + var id: String // if your identifier is not a String, conform the entity to EntityIdentifierConvertible. + var displayString: String + var displayRepresentation: DisplayRepresentation { + DisplayRepresentation(title: "\(displayString)") + } + + init(id: String, displayString: String) { + self.id = id + self.displayString = displayString + } struct TimerIdentifierAppEntityQuery: EntityQuery { func entities(for identifiers: [TimerIdentifierAppEntity.ID]) async throws -> [TimerIdentifierAppEntity] { - let timers = identifiers.compactMap { IntentDataProvider.main.timer(id: $0) } - return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } - // TODO: return TimerIdentifierAppEntity entities with the specified identifiers here. + await PersistenceController.shared.container.performBackgroundTask { context in + let timers = identifiers.compactMap { IntentDataProvider.main.timer(context: context, id: $0) } + return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } + } } func suggestedEntities() async throws -> [TimerIdentifierAppEntity] { - try await PersistenceController.shared.container.performBackgroundTask { context in let timers = try IntentDataProvider.main.timers(context: context) return timers.map { TimerIdentifierAppEntity(id: $0.stringId, displayString: $0.displayName) } } - - // TODO: return likely TimerIdentifierAppEntity entities here. - // This method is optional; the default implementation returns an empty array. } } - static var defaultQuery = TimerIdentifierAppEntityQuery() - - var id: String // if your identifier is not a String, conform the entity to EntityIdentifierConvertible. - var displayString: String - var displayRepresentation: DisplayRepresentation { - DisplayRepresentation(title: "\(displayString)") - } - - init(id: String, displayString: String) { - self.id = id - self.displayString = displayString - } } diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift index 6cc00a6..74923c5 100644 --- a/LeCountdown/Sound/DelaySoundPlayer.swift +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -39,6 +39,8 @@ import AVFoundation fileprivate func _play(in duration: TimeInterval, repeatCount: Int) throws { + self._activateAudioSession() + self._player.prepareToPlay() self._player.volume = 1.0 self._player.delegate = self @@ -70,4 +72,15 @@ import AVFoundation self.stop() } + fileprivate func _activateAudioSession() { + Logger.log("_activateAudioSession") + do { + let audioSession: AVAudioSession = AVAudioSession.sharedInstance() + try audioSession.setCategory(.playback, options: .duckOthers) + try audioSession.setActive(true) + } catch { + Logger.error(error) + } + } + } diff --git a/LeCountdown/TimerRouter.swift b/LeCountdown/TimerRouter.swift index 75ca1ac..7107b1d 100644 --- a/LeCountdown/TimerRouter.swift +++ b/LeCountdown/TimerRouter.swift @@ -33,9 +33,9 @@ class TimerRouter { static func performAction(timer: AbstractTimer, handler: @escaping (Result) -> Void) { switch timer { case let countdown as Countdown: - self._launchCountdown(countdown, handler: handler) + self._launchCountdown(countdown.stringId, handler: handler) case let stopwatch as Stopwatch: - self._startStopwatch(stopwatch, handler: handler) + self._startStopwatch(stopwatch.stringId, handler: handler) case let alarm as Alarm: self._scheduleAlarm(alarm, handler: handler) default: @@ -45,11 +45,17 @@ class TimerRouter { } - fileprivate static func _launchCountdown(_ countdown: Countdown, handler: @escaping (Result) -> Void) { + fileprivate static func _launchCountdown(_ countdownId: TimerID, handler: @escaping (Result) -> Void) { UNUserNotificationCenter.current().getNotificationSettings { settings in DispatchQueue.main.async { + + guard let countdown = IntentDataProvider.main.timer(id: countdownId) as? Countdown else { + handler(.failure(AppError.timerNotFound(id: countdownId))) + return + } + CountdownScheduler.master.scheduleIfPossible(countdown: countdown) { result in switch result { case .success: @@ -67,8 +73,8 @@ class TimerRouter { } - fileprivate static func _startStopwatch(_ stopwatch: Stopwatch, handler: @escaping (Result) -> Void) { - Conductor.maestro.startStopwatch(stopwatch) + fileprivate static func _startStopwatch(_ stopwatchId: TimerID, handler: @escaping (Result) -> Void) { + Conductor.maestro.startStopwatch(stopwatchId) handler(.success(Void())) } diff --git a/LeCountdown/Utils/AppError.swift b/LeCountdown/Utils/AppError.swift index dda59aa..bd8967e 100644 --- a/LeCountdown/Utils/AppError.swift +++ b/LeCountdown/Utils/AppError.swift @@ -11,6 +11,7 @@ enum AppError: LocalizedError { case defaultError(error: Error) case timerNotFound(id: String) + case timerNotManaged(timer: AbstractTimer) var errorDescription: String? { switch self { @@ -18,6 +19,9 @@ enum AppError: LocalizedError { return error.localizedDescription case .timerNotFound(let id): return "Timer not found: \(id)" + case .timerNotManaged(let timer): + return "Timer not managed: \(timer.stringId)" + } } @@ -27,6 +31,8 @@ enum AppError: LocalizedError { return error.localizedDescription case .timerNotFound: return "Timer not found" + case .timerNotManaged(let timer): + return "Timer not managed" } } diff --git a/LeCountdown/Widget/IntentDataProvider.swift b/LeCountdown/Widget/IntentDataProvider.swift index f56b93e..5667502 100644 --- a/LeCountdown/Widget/IntentDataProvider.swift +++ b/LeCountdown/Widget/IntentDataProvider.swift @@ -22,9 +22,13 @@ class IntentDataProvider { return try context.fetch(request) } + func timer(context: NSManagedObjectContext, id: String) -> AbstractTimer? { + return context.object(stringId: id) + } + func timer(id: String) -> AbstractTimer? { let context = PersistenceController.shared.container.viewContext - return context.object(stringId: id) + return self.timer(context: context, id: id) } }