diff --git a/LaunchIntents/IntentHandler.swift b/LaunchIntents/IntentHandler.swift index b9a28ce..ce3977d 100644 --- a/LaunchIntents/IntentHandler.swift +++ b/LaunchIntents/IntentHandler.swift @@ -8,34 +8,7 @@ import Intents class IntentHandler: INExtension, SelectTimerIntentHandling { -// -// // MARK: - SelectTimerIntentHandling -// -// func resolveTimer(for intent: LaunchTimerIntent) async -> TimerIdentifierResolutionResult { -// if let timer = intent.timer { -// print("resolveTimer(for intent: LaunchTimerIntent) success !") -// return .success(with: timer) -// } -// print("resolveTimer(for intent: LaunchTimerIntent) needsValue") -// return .needsValue() -// } -// -// func handle(intent: LaunchTimerIntent) async -> LaunchTimerIntentResponse { -// if let timerIdentifier = intent.timer, -// let timerId = timerIdentifier.identifier, -// let timer = IntentDataProvider.main.timer(id: timerId) { -// do { -// let _ = try await TimerRouter.performAction(timer: timer) -// print("handle(intent: LaunchTimerIntent) success !") -// return .success(timer: timerIdentifier) -// } catch { -// return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) -// } -// } -// print("handle(intent: LaunchTimerIntent) no timer") -// return LaunchTimerIntentResponse(code: LaunchTimerIntentResponseCode(rawValue: 1)!, userActivity: nil) -// } - + // MARK: - SelectTimerIntentHandling func resolveTimer(for intent: SelectTimerIntent) async -> [TimerPropertiesResolutionResult] { @@ -58,7 +31,7 @@ class IntentHandler: INExtension, SelectTimerIntentHandling { let displayName: String switch timer { case let countdown as Countdown: - let formattedDuration = countdown.duration.hourMinuteSecond + let formattedDuration = countdown.formattedDuration if let name = timer.activity?.name, !name.isEmpty { displayName = "\(name) (\(formattedDuration))" } else { @@ -86,7 +59,6 @@ class IntentHandler: INExtension, SelectTimerIntentHandling { } } - override func handler(for intent: INIntent) -> Any { // This is the default implementation. If you want different objects to handle different intents, // you can override this and return the handler you want for that particular intent. diff --git a/LaunchWidget/SingleTimerView.swift b/LaunchWidget/SingleTimerView.swift index d91990c..24388d2 100644 --- a/LaunchWidget/SingleTimerView.swift +++ b/LaunchWidget/SingleTimerView.swift @@ -58,7 +58,7 @@ struct SingleTimerView: View { VStack(alignment: .leading) { Text(timer.displayName.uppercased()) if let countdown = timer as? Countdown { - Text(countdown.duration.hourMinuteSecond) + Text(countdown.formattedDuration) } } Spacer() @@ -102,7 +102,7 @@ struct LockScreenCountdownView: View { default: Text(title) if let countdown = self.timer as? Countdown { - Text(countdown.duration.hourMinuteSecond) + Text(countdown.formattedDuration) .monospaced() } } @@ -175,7 +175,7 @@ struct MultiCountdownView: View { Spacer() Text(timer.displayName.uppercased()).lineLimit(1) if let countdown = timer as? Countdown { - Text(countdown.duration.hourMinuteSecond) + Text(countdown.formattedDuration) } Spacer() } diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index d545ba9..2c74e16 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -107,7 +107,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { } fileprivate func _timerId(notificationId: String) -> TimerID? { - let components = notificationId.components(separatedBy: CountdownScheduler.notificationIdSeparator) + let components = notificationId.components(separatedBy: Conductor.notificationIdSeparator) if components.count == 2 { return components[0] } else { diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index d091d54..7b1f4ca 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -29,6 +29,11 @@ enum CountdownState { case cancelled } +struct CountdownSpan { + var interval: DateInterval + var name: String? +} + class Conductor: ObservableObject { static let maestro: Conductor = Conductor() @@ -87,25 +92,11 @@ class Conductor: ObservableObject { } } - func removeLiveTimer(id: TimerID) { -// Logger.log("removeLiveTimer") - self.liveTimers.removeAll(where: { $0.id == id }) - self.cancelledCountdowns.removeAll(where: { $0 == id }) - self.currentStopwatches.removeValue(forKey: id) - self.pausedCountdowns.removeValue(forKey: id) - if let soundPlayer = self._delayedSoundPlayers[id] { - FileLogger.log("Stop sound player: \(self._timerName(id))") - soundPlayer.stop() - } - } - - fileprivate func _cleanupLiveTimers() { - self.liveTimers.removeAll() - } + static let notificationIdSeparator: String = "||" fileprivate func _buildLiveTimers() { - let liveCountdowns = self.currentCountdowns.map { + let liveCountdowns: [LiveTimer] = self.currentCountdowns.map { return LiveTimer(id: $0, date: $1.end) } @@ -133,6 +124,22 @@ class Conductor: ObservableObject { } + func removeLiveTimer(id: TimerID) { +// Logger.log("removeLiveTimer") + self.liveTimers.removeAll(where: { $0.id == id }) + self.cancelledCountdowns.removeAll(where: { $0 == id }) + self.currentStopwatches.removeValue(forKey: id) + self.pausedCountdowns.removeValue(forKey: id) + if let soundPlayer = self._delayedSoundPlayers[id] { + FileLogger.log("Stop sound player: \(self._timerName(id))") + soundPlayer.stop() + } + } + + fileprivate func _cleanupLiveTimers() { + self.liveTimers.removeAll() + } + func isCountdownCancelled(_ countdown: Countdown) -> Bool { return self.cancelledCountdowns.contains(where: { $0 == countdown.stringId }) } @@ -154,15 +161,33 @@ class Conductor: ObservableObject { func startCountdown(countdown: Countdown, handler: @escaping (Result) -> Void) { + self.cancelCurrentNotifications(countdownId: countdown.stringId) + let countdownId = countdown.stringId self._cleanupPreviousTimerIfNecessary(countdownId) do { - let end = try self._scheduleSoundPlayer(countdown: countdown, in: countdown.duration) + + var totalDuration = 0.0 + + for _ in 0...countdown.repeatCount { + for range in countdown.sortedRanges() { + // TODO: est-ce qu'on schedule tout ou en séquence ? + totalDuration += range.duration + let end = try self._scheduleSoundPlayer(countdown: countdown, range: range, in: totalDuration) + self._scheduleCountdownNotification(countdown: countdown, in: totalDuration, handler: handler) + + let dateInterval = DateInterval(start: Date(), end: end) + self.currentCountdowns[countdownId] = dateInterval + + self._launchLiveActivity(timer: countdown, date: end) + } + } + if Preferences.playConfirmationSound { self._playConfirmationSound(timer: countdown) } - handler(.success(end)) + handler(.success(Date(timeIntervalSinceNow: totalDuration))) } catch { FileLogger.log("start error : \(error.localizedDescription)") Logger.error(error) @@ -171,26 +196,21 @@ class Conductor: ObservableObject { } - fileprivate func _scheduleSoundPlayer(countdown: Countdown, in interval: TimeInterval) throws -> Date { + fileprivate func _scheduleSoundPlayer(countdown: Countdown, range: TimeRange, in interval: TimeInterval) throws -> Date { let start = Date() let end = start.addingTimeInterval(interval) let countdownId = countdown.stringId - FileLogger.log("schedule countdown \(self._timerName(countdownId)) at \(end)") + FileLogger.log("schedule countdown \(range.name ?? "''") at \(end)") - let sound = countdown.someSound + let sound = range.someSound ?? countdown.someSound ?? Sound.default let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound) self._delayedSoundPlayers[countdownId] = soundPlayer try soundPlayer.start(in: interval, repeatCount: Int(countdown.repeatCount)) - let dateInterval = DateInterval(start: start, end: end) - self.currentCountdowns[countdownId] = dateInterval - - self._launchLiveActivity(timer: countdown, date: end) - return end } @@ -205,7 +225,7 @@ class Conductor: ObservableObject { func cancelCountdown(id: TimerID) { FileLogger.log("Cancel \(self._timerName(id))") - CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) + self.cancelCurrentNotifications(countdownId: id) self.currentCountdowns.removeValue(forKey: id) self.removeLiveTimer(id: id) @@ -251,7 +271,7 @@ class Conductor: ObservableObject { self.pausedCountdowns[id] = remainingTime // cancel stuff - CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) + self.cancelCurrentNotifications(countdownId: id) self.cancelSoundPlayer(id: id) self._endLiveActivity(timerId: id) } @@ -325,7 +345,7 @@ class Conductor: ObservableObject { if let countdown: Countdown = context.object(stringId: countdownId) { do { - let sound: Sound = countdown.someSound + let sound: Sound = countdown.someSound ?? Sound.default let soundPlayer = try DelaySoundPlayer(timerID: countdownId, sound: sound) self._delayedSoundPlayers[countdownId] = soundPlayer try soundPlayer.restore(for: interval.end, repeatCount: Int(countdown.repeatCount)) @@ -361,6 +381,60 @@ class Conductor: ObservableObject { } } + // MARK: - Notifications + + fileprivate func _scheduleCountdownNotification(countdown: Countdown, in duration: TimeInterval, handler: @escaping (Result) -> Void) { + let content = UNMutableNotificationContent() + content.title = NSLocalizedString("It's time!", comment: "") + +// let duration = countdown.duration + let body: String + if let name = countdown.activity?.name { + let timesup = NSLocalizedString("Time's up for %@!", comment: "") + body = String(format: timesup, name) + } else { + let timesup = NSLocalizedString("Your %@ countdown is over!", comment: "") + body = String(format: timesup, duration.hourMinuteSecond) + } + + content.body = body + + self._createNotification(countdown: countdown, in: duration, content: content, handler: handler) + +// content.sound = UNNotificationSound.criticalSoundNamed(UNNotificationSoundName(rawValue: sound), withAudioVolume: 1.0) +// content.interruptionLevel = .critical + content.relevanceScore = 1.0 + + } + + fileprivate func _createNotification(countdown: Countdown, in duration: TimeInterval, offset: TimeInterval = 0.0, content: UNMutableNotificationContent, handler: @escaping (Result) -> Void) { + +// let duration = countdown.duration + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration + offset, repeats: false) + + let identifier: String = [countdown.objectID.uriRepresentation().absoluteString, "\(offset)"].joined(separator: Conductor.notificationIdSeparator) + + let request = UNNotificationRequest(identifier: identifier, + content: content, + trigger: trigger) + UNUserNotificationCenter.current().add(request) { error in + DispatchQueue.main.async { + if let error { + handler(.failure(error)) + print("Scheduling error = \(error)") + } + } + } + + } + + func cancelCurrentNotifications(countdownId: String) { + UNUserNotificationCenter.current().getPendingNotificationRequests { requests in + let ids = requests.map { $0.identifier }.filter { $0.hasPrefix(countdownId) } + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids) + } + } + // MARK: - Sound fileprivate func _playSound(timerId: String) { @@ -464,16 +538,16 @@ class Conductor: ObservableObject { // interaction.donate() // } - 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))") - } catch { - Logger.error(error) - } - } +// 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))") +// } catch { +// Logger.error(error) +// } +// } // MARK: - Live Activity diff --git a/LeCountdown/CountdownScheduler.swift b/LeCountdown/CountdownScheduler.swift index a40c340..c48b470 100644 --- a/LeCountdown/CountdownScheduler.swift +++ b/LeCountdown/CountdownScheduler.swift @@ -8,70 +8,19 @@ import Foundation import UserNotifications -class CountdownScheduler { - - static let master = CountdownScheduler() - - static let notificationIdSeparator: String = "||" - - func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result) -> Void) { - DispatchQueue.main.async { - self.cancelCurrentNotifications(countdownId: countdown.stringId) - Conductor.maestro.startCountdown(countdown: countdown, handler: handler) - self._scheduleCountdownNotification(countdown: countdown, handler: handler) - } - } - - fileprivate func _scheduleCountdownNotification(countdown: Countdown, handler: @escaping (Result) -> Void) { - let content = UNMutableNotificationContent() - content.title = NSLocalizedString("It's time!", comment: "") - - let duration = countdown.duration - let body: String - if let name = countdown.activity?.name { - let timesup = NSLocalizedString("Time's up for %@!", comment: "") - body = String(format: timesup, name) - } else { - let timesup = NSLocalizedString("Your %@ countdown is over!", comment: "") - body = String(format: timesup, duration.hourMinuteSecond) - } - - content.body = body - - self._createNotification(countdown: countdown, content: content, handler: handler) - -// content.sound = UNNotificationSound.criticalSoundNamed(UNNotificationSoundName(rawValue: sound), withAudioVolume: 1.0) -// content.interruptionLevel = .critical - content.relevanceScore = 1.0 - - } - - fileprivate func _createNotification(countdown: Countdown, offset: TimeInterval = 0.0, content: UNMutableNotificationContent, handler: @escaping (Result) -> Void) { - - let duration = countdown.duration - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: duration + offset, repeats: false) - - let identifier: String = [countdown.objectID.uriRepresentation().absoluteString, "\(offset)"].joined(separator: CountdownScheduler.notificationIdSeparator) - - let request = UNNotificationRequest(identifier: identifier, - content: content, - trigger: trigger) - UNUserNotificationCenter.current().add(request) { error in - DispatchQueue.main.async { - if let error { - handler(.failure(error)) - print("Scheduling error = \(error)") - } - } - } - - } - - func cancelCurrentNotifications(countdownId: String) { - UNUserNotificationCenter.current().getPendingNotificationRequests { requests in - let ids = requests.map { $0.identifier }.filter { $0.hasPrefix(countdownId) } - UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ids) - } - } - -} +//class CountdownScheduler { +// +// static let master = CountdownScheduler() +// +// static let notificationIdSeparator: String = "||" +// +// func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result) -> Void) { +// DispatchQueue.main.async { +// self.cancelCurrentNotifications(countdownId: countdown.stringId) +// Conductor.maestro.startCountdown(countdown: countdown, handler: handler) +// self._scheduleCountdownNotification(countdown: countdown, handler: handler) +// } +// } +// +// +//} diff --git a/LeCountdown/Model/Fakes.swift b/LeCountdown/Model/Fakes.swift index bd93646..0835f2c 100644 --- a/LeCountdown/Model/Fakes.swift +++ b/LeCountdown/Model/Fakes.swift @@ -12,7 +12,11 @@ extension Countdown { static func fake(context: NSManagedObjectContext) -> Countdown { let cd = Countdown(context: context) - cd.duration = 4 * 60.0 + + let timeRange = TimeRange(context: context) + timeRange.duration = 5.0 + timeRange.name = "Infusion" + let activity = Activity(context: context) activity.name = "Tea" cd.activity = activity diff --git a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift index 7391988..79481a5 100644 --- a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift @@ -2,7 +2,7 @@ // Countdown+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 22/11/2023. +// Created by Laurent Morvillier on 23/11/2023. // // @@ -16,7 +16,6 @@ extension Countdown { return NSFetchRequest(entityName: "Countdown") } - @NSManaged public var duration: Double @NSManaged public var timeRanges: NSSet? } diff --git a/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift b/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift index 12e59a5..6b3a18c 100644 --- a/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift @@ -2,7 +2,7 @@ // TimeRange+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 22/11/2023. +// Created by Laurent Morvillier on 23/11/2023. // // @@ -17,9 +17,9 @@ extension TimeRange { } @NSManaged public var duration: Double - @NSManaged public var soundList: String? @NSManaged public var name: String? @NSManaged public var order: Int16 + @NSManaged public var playableIds: String? @NSManaged public var countdown: Countdown? } diff --git a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents index 5eb7ab0..9fab959 100644 --- a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents @@ -19,7 +19,6 @@ - @@ -44,7 +43,7 @@ - + \ No newline at end of file diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 1c814fd..9c1bfc6 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -9,16 +9,16 @@ import Foundation import SwiftUI import CoreData -extension AbstractSoundTimer { +protocol StoresSound: ManagedObject { + var playableIds: String? { get } +} + +extension StoresSound { var playables: [any Playable] { return playables(idList: self.playableIds) } - var confirmationPlayables: [any Playable] { - return playables(idList: self.confirmationSoundList) - } - func playables(idList: String?) -> [any Playable] { if let idList { var playables: [any Playable] = [] @@ -40,18 +40,7 @@ extension AbstractSoundTimer { return self.playables.reduce(Set()) { $0.union($1.soundList) } } - var confirmationSounds: Set { - if let confirmationSoundList { - return Set(confirmationSoundList.enumItems()) - } - return [] - } - - func setConfirmationSounds(_ sounds: Set) { - self.confirmationSoundList = sounds.stringRepresentation - } - - var someSound: Sound { + var someSound: Sound? { var sounds: Set = self.allSounds if !AppGuard.main.isSubscriber { sounds = sounds.filter { !$0.isRestricted } @@ -76,8 +65,49 @@ extension AbstractSoundTimer { return random } - return Sound.default + return nil + } + +} + +extension AbstractSoundTimer: StoresSound { + +// var playables: [any Playable] { +// return playables(idList: self.playableIds) +// } + + var confirmationPlayables: [any Playable] { + return playables(idList: self.confirmationSoundList) } + +// func playables(idList: String?) -> [any Playable] { +// if let idList { +// var playables: [any Playable] = [] +// let ids: [String] = idList.components(separatedBy: idSeparator) +// for id in ids { +// if let intId = numberFormatter.number(from: id)?.intValue, +// let sound = Sound(rawValue: intId) { +// playables.append(sound) +// } else if let playlist = Playlist(rawValue: id) { +// playables.append(playlist) +// } +// } +// return playables +// } +// return [] +// } + + var confirmationSounds: Set { + if let confirmationSoundList { + return Set(confirmationSoundList.enumItems()) + } + return [] + } + + func setConfirmationSounds(_ sounds: Set) { + self.confirmationSoundList = sounds.stringRepresentation + } + } @@ -158,7 +188,7 @@ extension CustomSound : Localized { } -extension TimeRange { +extension TimeRange: StoresSound { static func fake(context: NSManagedObjectContext) -> TimeRange { let timeRange = TimeRange(context: context) diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index fee7f19..ad099fc 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -46,6 +46,13 @@ extension AbstractTimer { extension Countdown { + func sortedRanges() -> [TimeRange] { + guard let ranges = self.timeRanges as? Set else { + return [] + } + return ranges.sorted(using: SortDescriptor(\TimeRange.order, order: .forward)) + } + override var defaultName: String { return NSLocalizedString("Countdown", comment: "") } @@ -54,6 +61,22 @@ extension Countdown { return self.timeRanges?.count ?? 0 } + var formattedDuration: String { + let durations: [String] = self.sortedRanges().map { $0.duration.hourMinuteSecond } + var formatted: String + if durations.count > 2 { + formatted = durations.joined(separator: " / ") + } else { + formatted = durations.first ?? "none" + } + + if self.repeatCount > 1 { + return "\(formatted) * \(repeatCount)" + } else { + return formatted + } + } + } extension Stopwatch { diff --git a/LeCountdown/Model/NSManagedContext+Extensions.swift b/LeCountdown/Model/NSManagedContext+Extensions.swift index 9b407fc..ee01fe2 100644 --- a/LeCountdown/Model/NSManagedContext+Extensions.swift +++ b/LeCountdown/Model/NSManagedContext+Extensions.swift @@ -63,7 +63,13 @@ extension NSManagedObjectContext { } -extension NSManagedObject { +protocol ManagedObject { + var isTemporary: Bool { get } + var stringId: String { get } + static var entityName: String { get } +} + +extension NSManagedObject : ManagedObject { var isTemporary: Bool { return self.objectID.isTemporaryID @@ -76,5 +82,5 @@ extension NSManagedObject { static var entityName: String { return self.entity().managedObjectClassName } - + } diff --git a/LeCountdown/Sound/Sound.swift b/LeCountdown/Sound/Sound.swift index 4d9b7fa..6951b48 100644 --- a/LeCountdown/Sound/Sound.swift +++ b/LeCountdown/Sound/Sound.swift @@ -12,7 +12,7 @@ protocol Playable: StringRepresentable, Equatable, Hashable { var soundList: Set { get } } -extension Playlist : Playable { +extension Playlist: Playable { var stringValue: String { self.rawValue } var soundList: Set { return Set(SoundCatalog.main.sounds(for: self)) diff --git a/LeCountdown/TimerRouter.swift b/LeCountdown/TimerRouter.swift index c4692c2..18bd660 100644 --- a/LeCountdown/TimerRouter.swift +++ b/LeCountdown/TimerRouter.swift @@ -59,7 +59,7 @@ class TimerRouter { return } - CountdownScheduler.master.scheduleIfPossible(countdown: countdown) { result in + Conductor.maestro.startCountdown(countdown: countdown) { result in switch result { case .success: handler(.success(Void())) diff --git a/LeCountdown/Views/Countdown/CountdownDialView.swift b/LeCountdown/Views/Countdown/CountdownDialView.swift index a88bf53..57ee46f 100644 --- a/LeCountdown/Views/Countdown/CountdownDialView.swift +++ b/LeCountdown/Views/Countdown/CountdownDialView.swift @@ -26,7 +26,7 @@ struct CountdownDialView: View, DialStyle { Text(countdown.activity?.name?.uppercased() ?? "") .foregroundColor(self._titleColor) .multilineTextAlignment(.leading) - Text(countdown.duration.hourMinuteSecond) + Text(countdown.formattedDuration) .fontWeight(.semibold) .foregroundColor(self._durationColor) } diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index 391f0bd..f8cc078 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -205,7 +205,16 @@ struct CountdownEditView : View { fileprivate func _loadCountdown(_ countdown: Countdown) { - self.duration = countdown.duration +// self.duration = countdown.duration + + let ranges = countdown.sortedRanges() + if ranges.count > 1 { + self.model.ranges = ranges + } else if let range = ranges.first { + self.duration = range.duration + self.nameString = range.name ?? "" + } + if let name = countdown.activity?.name, !name.isEmpty { self.nameString = name } @@ -235,7 +244,7 @@ struct CountdownEditView : View { cd = Countdown(context: viewContext) } - cd.duration = self.duration +// cd.duration = self.duration if self._isNewCountdown { let max: Int16 @@ -269,6 +278,11 @@ struct CountdownEditView : View { range.order = Int16(index) cd.addToTimeRanges(range) } + } else { + let timeRange = TimeRange(context: viewContext) + timeRange.duration = self.duration + timeRange.name = self.nameString + cd.addToTimeRanges(timeRange) } if !self.nameString.isEmpty { diff --git a/LeCountdown/Views/LiveTimerListView.swift b/LeCountdown/Views/LiveTimerListView.swift index 6ba4576..024b073 100644 --- a/LeCountdown/Views/LiveTimerListView.swift +++ b/LeCountdown/Views/LiveTimerListView.swift @@ -200,16 +200,6 @@ struct LiveCountdownView: View { TimeView(text: NSLocalizedString("Cancelled", comment: "")) } -// if cancelled { -// TimeView(text: NSLocalizedString("Cancelled", comment: "")) -// } else if let remainingTime { -// TimeView(text: remainingTime.hourMinuteSecond) -// } else if running { -// TimeView(text: self._formattedDuration(date: context.date)) -// } else { -// TimeView(text: self.date.formatted(date: .omitted, time: .shortened)) -// } - Text(self.countdown.displayName.uppercased()) .foregroundColor(self.colorScheme == .dark ? .white : .black) diff --git a/LeCountdown/Views/StartView.swift b/LeCountdown/Views/StartView.swift index 64300c7..4b6218e 100644 --- a/LeCountdown/Views/StartView.swift +++ b/LeCountdown/Views/StartView.swift @@ -142,7 +142,11 @@ class Customization: ObservableObject { let context = PersistenceController.shared.container.viewContext let countdown = Countdown(context: context) countdown.activity = CoreDataRequests.getOrCreateActivity(name: preset.localizedName) - countdown.duration = self.duration + + for range in preset.ranges(context: context) { + countdown.addToTimeRanges(range) + } + countdown.playableIds = self.timerModel.soundModel.playableIds return countdown }