|
|
|
|
@ -52,6 +52,7 @@ class Conductor: ObservableObject { |
|
|
|
|
fileprivate var _cleanupTimers: [String : Timer] = [:] |
|
|
|
|
|
|
|
|
|
func removeLiveTimer(id: String) { |
|
|
|
|
self.stopSoundIfPossible() |
|
|
|
|
self.liveTimers.removeAll(where: { $0.id == id }) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -89,21 +90,36 @@ class Conductor: ObservableObject { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func notifyUser(countdownId: String) { |
|
|
|
|
self._playSound(timerId: countdownId) |
|
|
|
|
self._endCountdown(countdownId: countdownId, cancel: false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _recordActivity(countdownId: String) { |
|
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
|
if let countdown = context.object(stringId: countdownId) as? Countdown, |
|
|
|
|
let dateInterval = self.currentCountdowns[countdownId] { |
|
|
|
|
do { |
|
|
|
|
try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval) |
|
|
|
|
} catch { |
|
|
|
|
print("Could not record activity = \(error)") |
|
|
|
|
// TODO: show error to user |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Countdown |
|
|
|
|
|
|
|
|
|
func startCountdown(_ date: Date, countdown: Countdown) { |
|
|
|
|
DispatchQueue.main.async { |
|
|
|
|
let dateInterval = DateInterval(start: Date(), end: date) |
|
|
|
|
self.currentCountdowns[countdown.stringId] = dateInterval |
|
|
|
|
|
|
|
|
|
self._launchLiveActivity(countdown: countdown, endDate: date) |
|
|
|
|
// self._launchLiveActivity(countdown: countdown, endDate: date) |
|
|
|
|
self._cleanupTimers.removeValue(forKey: countdown.stringId) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func notifyUser(countdownId: String) { |
|
|
|
|
self._playSound(countdownId: countdownId) |
|
|
|
|
self._endCountdown(countdownId: countdownId, cancel: false) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func cancelCountdown(id: String) { |
|
|
|
|
CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) |
|
|
|
|
@ -115,16 +131,17 @@ class Conductor: ObservableObject { |
|
|
|
|
if !cancel { |
|
|
|
|
self._recordActivity(countdownId: countdownId) |
|
|
|
|
} |
|
|
|
|
self.currentCountdowns.removeValue(forKey: countdownId) |
|
|
|
|
|
|
|
|
|
if self.currentCountdowns.removeValue(forKey: countdownId) != nil { |
|
|
|
|
self._endLiveActivity(countdownId: countdownId) |
|
|
|
|
} |
|
|
|
|
// if self.currentCountdowns.removeValue(forKey: countdownId) != nil { |
|
|
|
|
// self._endLiveActivity(countdownId: countdownId) |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
self.removeLiveTimer(id: countdownId) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func cleanup() { |
|
|
|
|
func cleanupCountdowns() { |
|
|
|
|
let now = Date() |
|
|
|
|
for (key, value) in self.currentCountdowns { |
|
|
|
|
if value.end < now { |
|
|
|
|
@ -133,37 +150,55 @@ class Conductor: ObservableObject { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _recordActivity(countdownId: String) { |
|
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
|
if let countdown = context.object(stringId: countdownId) as? Countdown, |
|
|
|
|
let dateInterval = self.currentCountdowns[countdownId] { |
|
|
|
|
// MARK: - Stopwatch |
|
|
|
|
|
|
|
|
|
func startStopwatch(_ stopwatch: Stopwatch) { |
|
|
|
|
DispatchQueue.main.async { |
|
|
|
|
let now = Date() |
|
|
|
|
Conductor.maestro.currentStopwatches[stopwatch.stringId] = now |
|
|
|
|
self._launchLiveActivity(stopwatch: stopwatch, start: now) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) { |
|
|
|
|
if let start = Conductor.maestro.currentStopwatches[stopwatch.stringId] { |
|
|
|
|
Conductor.maestro.currentStopwatches.removeValue(forKey: stopwatch.stringId) |
|
|
|
|
do { |
|
|
|
|
try CoreDataRequests.recordActivity(timer: countdown, dateInterval: dateInterval) |
|
|
|
|
try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: start, end: end ?? Date())) |
|
|
|
|
} catch { |
|
|
|
|
print("Could not record activity = \(error)") |
|
|
|
|
// TODO: show error to user |
|
|
|
|
print("could not record") |
|
|
|
|
} |
|
|
|
|
self._endLiveActivity(timerId: stopwatch.stringId) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Sound |
|
|
|
|
|
|
|
|
|
fileprivate func _playSound(countdownId: String) { |
|
|
|
|
|
|
|
|
|
let countdown = PersistenceController.shared.container.viewContext.object(stringId: countdownId) as? Countdown |
|
|
|
|
fileprivate func _playSound(timerId: String) { |
|
|
|
|
|
|
|
|
|
let coolSound = countdown?.coolSound ?? Sound.allCases[0] |
|
|
|
|
let context = PersistenceController.shared.container.viewContext |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
let soundFile = try coolSound.soundFile() |
|
|
|
|
let soundPlayer = SoundPlayer() |
|
|
|
|
self.soundPlayer = soundPlayer |
|
|
|
|
try soundPlayer.playSound(soundFile: soundFile, repeats: countdown?.repeats ?? true) |
|
|
|
|
} catch { |
|
|
|
|
print("error = \(error)") |
|
|
|
|
// TODO: manage error |
|
|
|
|
var coolSound: Sound? = nil |
|
|
|
|
switch context.object(stringId: timerId) { |
|
|
|
|
case let cd as Countdown: |
|
|
|
|
coolSound = cd.coolSound |
|
|
|
|
case let sw as Stopwatch: |
|
|
|
|
coolSound = sw.coolSound |
|
|
|
|
default: |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if let coolSound { |
|
|
|
|
do { |
|
|
|
|
let soundFile = try coolSound.soundFile() |
|
|
|
|
let soundPlayer = SoundPlayer() |
|
|
|
|
self.soundPlayer = soundPlayer |
|
|
|
|
try soundPlayer.playSound(soundFile: soundFile, repeats: false) |
|
|
|
|
} catch { |
|
|
|
|
print("error = \(error)") |
|
|
|
|
// TODO: manage error |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func stopSoundIfPossible() { |
|
|
|
|
@ -173,13 +208,13 @@ class Conductor: ObservableObject { |
|
|
|
|
|
|
|
|
|
// MARK: - Live Activity |
|
|
|
|
|
|
|
|
|
fileprivate func _launchLiveActivity(countdown: Countdown, endDate: Date) { |
|
|
|
|
fileprivate func _launchLiveActivity(stopwatch: Stopwatch, start: Date) { |
|
|
|
|
|
|
|
|
|
if ActivityAuthorizationInfo().areActivitiesEnabled { |
|
|
|
|
|
|
|
|
|
let contentState = LaunchWidgetAttributes.ContentState(ended: false) |
|
|
|
|
let attributes = LaunchWidgetAttributes(id: countdown.stringId, name: countdown.displayName, endDate: endDate) |
|
|
|
|
let activityContent = ActivityContent(state: contentState, staleDate: endDate.addingTimeInterval(30.0)) |
|
|
|
|
let attributes = LaunchWidgetAttributes(id: stopwatch.stringId, name: stopwatch.displayName, date: start) |
|
|
|
|
let activityContent = ActivityContent(state: contentState, staleDate: nil) |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
let liveActivity = try ActivityKit.Activity.request(attributes: attributes, content: activityContent) |
|
|
|
|
@ -188,7 +223,7 @@ class Conductor: ObservableObject { |
|
|
|
|
print("Error requesting countdown Live Activity \(error.localizedDescription).") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
self._scheduleAppRefresh(countdown: countdown) |
|
|
|
|
// self._scheduleAppRefresh(countdown: countdown) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
@ -196,7 +231,6 @@ class Conductor: ObservableObject { |
|
|
|
|
fileprivate func _scheduleAppRefresh(countdown: Countdown) { |
|
|
|
|
let request = BGAppRefreshTaskRequest(identifier: BGTaskIdentifier.refresh.rawValue) |
|
|
|
|
request.earliestBeginDate = Date(timeIntervalSinceNow: countdown.duration) |
|
|
|
|
|
|
|
|
|
do { |
|
|
|
|
try BGTaskScheduler.shared.submit(request) |
|
|
|
|
print("request submitted with date: \(String(describing: request.earliestBeginDate))") |
|
|
|
|
@ -205,8 +239,8 @@ class Conductor: ObservableObject { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _liveActivity(countdownId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? { |
|
|
|
|
return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == countdownId } ) |
|
|
|
|
fileprivate func _liveActivity(timerId: String) -> ActivityKit.Activity<LaunchWidgetAttributes>? { |
|
|
|
|
return ActivityKit.Activity<LaunchWidgetAttributes>.activities.first(where: { $0.attributes.id == timerId } ) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func updateLiveActivities() { |
|
|
|
|
@ -215,7 +249,7 @@ class Conductor: ObservableObject { |
|
|
|
|
for (countdownId, interval) in self.currentCountdowns { |
|
|
|
|
|
|
|
|
|
if interval.end < Date() { |
|
|
|
|
self._endLiveActivity(countdownId: countdownId) |
|
|
|
|
self._endLiveActivity(timerId: countdownId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -237,11 +271,11 @@ class Conductor: ObservableObject { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _endLiveActivity(countdownId: String) { |
|
|
|
|
fileprivate func _endLiveActivity(timerId: String) { |
|
|
|
|
|
|
|
|
|
print("Try to end the Live Activity: \(countdownId)") |
|
|
|
|
print("Try to end the Live Activity: \(timerId)") |
|
|
|
|
|
|
|
|
|
if let activity = self._liveActivity(countdownId: countdownId) { |
|
|
|
|
if let activity = self._liveActivity(timerId: timerId) { |
|
|
|
|
Task { |
|
|
|
|
let state = LaunchWidgetAttributes.ContentState(ended: true) |
|
|
|
|
let content = ActivityContent(state: state, staleDate: Date()) |
|
|
|
|
|