From 2871a7abaa1b2fb70394bd4da93a41e6add56d7b Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 30 Nov 2023 18:11:11 +0100 Subject: [PATCH] Make the restore / resume work --- LeCountdown.xcodeproj/project.pbxproj | 44 ++-- LeCountdown/Conductor.swift | 239 +++++++++--------- LeCountdown/CountdownSequence.swift | 118 +++++++++ LeCountdown/Model/Fakes.swift | 7 +- .../Countdown+CoreDataProperties.swift | 22 +- .../Model/Generation/Step+CoreDataClass.swift | 15 ++ ...es.swift => Step+CoreDataProperties.swift} | 12 +- .../Generation/TimeRange+CoreDataClass.swift | 15 -- .../LeCountdown.0.6.6.xcdatamodel/contents | 16 +- LeCountdown/Model/Model+Extensions.swift | 12 +- .../Model/Model+SharedExtensions.swift | 16 +- LeCountdown/Patcher.swift | 18 +- LeCountdown/Sound/DelaySoundPlayer.swift | 2 +- .../Views/Countdown/CountdownDialView.swift | 1 + .../Views/Countdown/CountdownFormView.swift | 16 +- .../Views/Countdown/NewCountdownView.swift | 50 ++-- .../Views/Countdown/RangeFormView.swift | 12 +- LeCountdown/Views/LiveTimerListView.swift | 2 +- LeCountdown/Views/PresetsView.swift | 9 +- LeCountdown/Views/StartView.swift | 6 +- LeCountdown/Views/TimerModel.swift | 40 ++- LeCountdown/en.lproj/Localizable.strings | 3 +- LeCountdown/fr.lproj/Localizable.strings | 3 +- TimeRange+CoreDataClass.swift | 15 -- TimeRange+CoreDataProperties.swift | 29 --- 25 files changed, 412 insertions(+), 310 deletions(-) create mode 100644 LeCountdown/CountdownSequence.swift create mode 100644 LeCountdown/Model/Generation/Step+CoreDataClass.swift rename LeCountdown/Model/Generation/{TimeRange+CoreDataProperties.swift => Step+CoreDataProperties.swift} (61%) delete mode 100644 LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift delete mode 100644 TimeRange+CoreDataClass.swift delete mode 100644 TimeRange+CoreDataProperties.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index db4a721..7b37d2c 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -100,6 +100,15 @@ C4556F7629E411A400DEB40B /* LogsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4556F7529E411A400DEB40B /* LogsView.swift */; }; C45D6AB52B173DFD00A5B649 /* Patcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6AB42B173DFD00A5B649 /* Patcher.swift */; }; C45D6AB92B17499200A5B649 /* Model+CSV.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6AB82B17499200A5B649 /* Model+CSV.swift */; }; + C45D6AC02B18A09900A5B649 /* Step+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABD2B18A09900A5B649 /* Step+CoreDataClass.swift */; }; + C45D6AC12B18A09900A5B649 /* Step+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABE2B18A09900A5B649 /* Step+CoreDataProperties.swift */; }; + C45D6AC22B18A13C00A5B649 /* Step+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABD2B18A09900A5B649 /* Step+CoreDataClass.swift */; }; + C45D6AC32B18A13C00A5B649 /* Step+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABE2B18A09900A5B649 /* Step+CoreDataProperties.swift */; }; + C45D6AC42B18A13C00A5B649 /* Step+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABD2B18A09900A5B649 /* Step+CoreDataClass.swift */; }; + C45D6AC52B18A13C00A5B649 /* Step+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6ABE2B18A09900A5B649 /* Step+CoreDataProperties.swift */; }; + C45D6AC82B18D02900A5B649 /* CountdownSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6AC72B18D02900A5B649 /* CountdownSequence.swift */; }; + C45D6ACA2B18D08000A5B649 /* CountdownSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6AC72B18D02900A5B649 /* CountdownSequence.swift */; }; + C45D6ACB2B18D08100A5B649 /* CountdownSequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C45D6AC72B18D02900A5B649 /* CountdownSequence.swift */; }; C4636D9C29AF46BD00994E31 /* ActivityKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C4636D9B29AF46BD00994E31 /* ActivityKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C4636D9D29AF46D900994E31 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C438C7D02981216200BF3EF9 /* WidgetKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C46926BD29DDC49E0003E310 /* SubscriptionButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46926BC29DDC49E0003E310 /* SubscriptionButtonView.swift */; }; @@ -224,15 +233,9 @@ C4BA2B7329A60CF000CB4FBA /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7229A60CF000CB4FBA /* Shortcut.swift */; }; C4BA2B7929A65C1400CB4FBA /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */; }; C4BCABB92A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4BCABB82A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav */; }; - C4C826612B0E411A0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; - C4C826692B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; - C4C8266A2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; - C4C8266B2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */; }; C4C8266C2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; C4C8266D2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; C4C8266E2B0E41D20036C666 /* Countdown+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */; }; - C4C8266F2B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; - C4C826702B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */; }; C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */; }; C4E5D66729B73AED008E7465 /* TimerIdentifierAppEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */; }; C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5D66929B73FC6008E7465 /* TimerShortcuts.swift */; }; @@ -422,6 +425,9 @@ C4556F7529E411A400DEB40B /* LogsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogsView.swift; sourceTree = ""; }; C45D6AB42B173DFD00A5B649 /* Patcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Patcher.swift; sourceTree = ""; }; C45D6AB82B17499200A5B649 /* Model+CSV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Model+CSV.swift"; sourceTree = ""; }; + C45D6ABD2B18A09900A5B649 /* Step+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Step+CoreDataClass.swift"; sourceTree = ""; }; + C45D6ABE2B18A09900A5B649 /* Step+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Step+CoreDataProperties.swift"; sourceTree = ""; }; + C45D6AC72B18D02900A5B649 /* CountdownSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownSequence.swift; sourceTree = ""; }; C4636D9B29AF46BD00994E31 /* ActivityKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActivityKit.framework; path = System/Library/Frameworks/ActivityKit.framework; sourceTree = SDKROOT; }; C46926BC29DDC49E0003E310 /* SubscriptionButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionButtonView.swift; sourceTree = ""; }; C473C2F829A8DC0A0056B38A /* LaunchWidgetAttributes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchWidgetAttributes.swift; sourceTree = ""; }; @@ -495,10 +501,6 @@ C4BA2B7829A65C1400CB4FBA /* UIDevice+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extensions.swift"; sourceTree = ""; }; C4BCABB82A040B97009FFB0A /* QP01 0023 Surf moderate sandy.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = "QP01 0023 Surf moderate sandy.wav"; sourceTree = ""; }; C4C8265A2B0E40350036C666 /* LeCountdown.0.6.6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.6.6.xcdatamodel; sourceTree = ""; }; - C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataClass.swift"; sourceTree = ""; }; - C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = ""; }; - C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = ""; }; - C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "TimeRange+CoreDataProperties.swift"; sourceTree = ""; }; C4C826682B0E41D20036C666 /* Countdown+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Countdown+CoreDataProperties.swift"; sourceTree = ""; }; C4E5D66429B73AED008E7465 /* StartTimerIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTimerIntent.swift; sourceTree = ""; }; C4E5D66529B73AED008E7465 /* TimerIdentifierAppEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerIdentifierAppEntity.swift; sourceTree = ""; }; @@ -620,6 +622,7 @@ C45D6AB42B173DFD00A5B649 /* Patcher.swift */, C438C7C02980228B00BF3EF9 /* CountdownScheduler.swift */, C4F8B15629891271005C86A5 /* Conductor.swift */, + C45D6AC72B18D02900A5B649 /* CountdownSequence.swift */, C4F8B1D7298C0727005C86A5 /* TimerRouter.swift */, C438C80B2981DE2E00BF3EF9 /* Views */, C438C8092981DDF800BF3EF9 /* Model */, @@ -838,8 +841,6 @@ C48920632B0E422E00F6F4D8 /* Recovered References */ = { isa = PBXGroup; children = ( - C4C826632B0E41610036C666 /* Countdown+CoreDataProperties.swift */, - C4C826642B0E41610036C666 /* TimeRange+CoreDataProperties.swift */, ); name = "Recovered References"; sourceTree = ""; @@ -931,10 +932,10 @@ C4BA2AEE2996A11900CB4FBA /* CustomSound+CoreDataProperties.swift */, C4F8B16E298AC234005C86A5 /* Record+CoreDataClass.swift */, C4F8B16F298AC234005C86A5 /* Record+CoreDataProperties.swift */, + C45D6ABD2B18A09900A5B649 /* Step+CoreDataClass.swift */, + C45D6ABE2B18A09900A5B649 /* Step+CoreDataProperties.swift */, C4286E9F2A1502FD0070D075 /* Stopwatch+CoreDataClass.swift */, C4BA2B48299FCE0C00CB4FBA /* Stopwatch+CoreDataProperties.swift */, - C4C8265D2B0E411A0036C666 /* TimeRange+CoreDataClass.swift */, - C4C826672B0E41D20036C666 /* TimeRange+CoreDataProperties.swift */, ); path = Generation; sourceTree = ""; @@ -1322,6 +1323,7 @@ C4E5D66629B73AED008E7465 /* StartTimerIntent.swift in Sources */, C4286EA62A150A7E0070D075 /* TimePickerView.swift in Sources */, C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */, + C45D6AC02B18A09900A5B649 /* Step+CoreDataClass.swift in Sources */, C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */, C4BA2B3E299FC86800CB4FBA /* StatisticsView.swift in Sources */, C4F8B1B8298AC81D005C86A5 /* CountdownDialView.swift in Sources */, @@ -1332,6 +1334,7 @@ C4BA2AFD299A3A3700CB4FBA /* AppleMusicPlayer.swift in Sources */, C4BA2B3A299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, C4BA2AF32996A11900CB4FBA /* AbstractSoundTimer+CoreDataProperties.swift in Sources */, + C45D6AC12B18A09900A5B649 /* Step+CoreDataProperties.swift in Sources */, C45D6AB52B173DFD00A5B649 /* Patcher.swift in Sources */, C4556F6B29E40B7800DEB40B /* FileLogger.swift in Sources */, C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */, @@ -1362,8 +1365,6 @@ C4060DF5297AE9A7003FAB80 /* TimeInterval+Extensions.swift in Sources */, C4F8B166298A9ABB005C86A5 /* SoundFormView.swift in Sources */, C4F8B17D298AC234005C86A5 /* Record+CoreDataProperties.swift in Sources */, - C4C826612B0E411A0036C666 /* TimeRange+CoreDataClass.swift in Sources */, - C4C826692B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4F8B184298AC234005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C4F8B17A298AC234005C86A5 /* Countdown+CoreDataClass.swift in Sources */, C42E970229E6B32B005B1B8C /* CalendarView.swift in Sources */, @@ -1391,6 +1392,7 @@ C4F8B1AC298AC3A0005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */, C4060DC0297AE73B003FAB80 /* LeCountdownApp.swift in Sources */, + C45D6AC82B18D02900A5B649 /* CountdownSequence.swift in Sources */, C473C31829A926F50056B38A /* LaunchWidget.intentdefinition in Sources */, C4E5D66A29B73FC6008E7465 /* TimerShortcuts.swift in Sources */, C4742B5729840F6400D5D950 /* CoolPic.swift in Sources */, @@ -1427,8 +1429,8 @@ C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */, C4BA2B3B299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, C438C7EB2981266F00BF3EF9 /* SingleTimerView.swift in Sources */, - C4C826702B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */, C47C933729F01B7A00C780E2 /* Codable+Extensions.swift in Sources */, + C45D6AC32B18A13C00A5B649 /* Step+CoreDataProperties.swift in Sources */, C438C7D62981216200BF3EF9 /* LaunchWidgetBundle.swift in Sources */, C4286EAE2A17753A0070D075 /* AppError.swift in Sources */, C4F8B18B298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, @@ -1449,19 +1451,20 @@ C473C2FA29A8DC1E0056B38A /* LaunchWidgetAttributes.swift in Sources */, C4286EA42A1503330070D075 /* Stopwatch+CoreDataClass.swift in Sources */, C438C7E82981255D00BF3EF9 /* TimeInterval+Extensions.swift in Sources */, + C45D6AC22B18A13C00A5B649 /* Step+CoreDataClass.swift in Sources */, C4BA2B37299F82FF00CB4FBA /* Fakes.swift in Sources */, C4F8B18F298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C438C7D82981216200BF3EF9 /* LaunchWidgetLiveActivity.swift in Sources */, C4F8B18C298AC288005C86A5 /* Alarm+CoreDataClass.swift in Sources */, C438C8192982BFDB00BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, C438C7DA2981216200BF3EF9 /* LaunchWidget.swift in Sources */, + C45D6ACB2B18D08100A5B649 /* CountdownSequence.swift in Sources */, C4BA2AF62996A4EF00CB4FBA /* CustomSound+CoreDataProperties.swift in Sources */, C4F8B192298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C47C933629F01B6600C780E2 /* FileUtils.swift in Sources */, C4F8B18E298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */, C4A16DC829D311C800143D5E /* Extensions.swift in Sources */, C4F8B1AE298AC451005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, - C4C8266A2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4BA2B32299F75DE00CB4FBA /* DefaultView.swift in Sources */, C48ECC0829DAC45900DE5A66 /* AppGuard.swift in Sources */, C438C8182982BFC100BF3EF9 /* Persistence.swift in Sources */, @@ -1476,6 +1479,7 @@ C49C346A29DECC7100AAC6FC /* LiveStopWatch.swift in Sources */, C473C2F329A8DA6F0056B38A /* LiveTimer.swift in Sources */, C4F8B1C6298ACC1F005C86A5 /* SoundPlayer.swift in Sources */, + C45D6ACA2B18D08000A5B649 /* CountdownSequence.swift in Sources */, C4F8B1A2298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, C4F8B1B2298AC451005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, C473C2F229A8DA1F0056B38A /* CountdownScheduler.swift in Sources */, @@ -1485,7 +1489,6 @@ C4BA2B38299F82FF00CB4FBA /* Fakes.swift in Sources */, C438C7F529812BB200BF3EF9 /* IntentHandler.swift in Sources */, C473C33D29ACEC4F0056B38A /* Tip.swift in Sources */, - C4C8266B2B0E41D20036C666 /* TimeRange+CoreDataProperties.swift in Sources */, C4F8B19C298AC288005C86A5 /* AbstractTimer+CoreDataProperties.swift in Sources */, C4556F7429E40EC500DEB40B /* Codable+Extensions.swift in Sources */, C4F8B1A3298AC288005C86A5 /* Activity+CoreDataClass.swift in Sources */, @@ -1495,8 +1498,8 @@ C438C802298132B900BF3EF9 /* LeCountdown.xcdatamodeld in Sources */, C4F8B1A0298AC288005C86A5 /* Activity+CoreDataProperties.swift in Sources */, C42E96FE29E5B5CD005B1B8C /* Filter.swift in Sources */, + C45D6AC52B18A13C00A5B649 /* Step+CoreDataProperties.swift in Sources */, C438C81A2982BFF100BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, - C4C8266F2B0E41DB0036C666 /* TimeRange+CoreDataClass.swift in Sources */, C4F8B19D298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, C4286EB22A1B75C60070D075 /* BoringContext.swift in Sources */, @@ -1513,6 +1516,7 @@ C4556F7229E40EBF00DEB40B /* FileLogger.swift in Sources */, C4F8B1B1298AC451005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, C473C2F129A8DA0B0056B38A /* Conductor.swift in Sources */, + C45D6AC42B18A13C00A5B649 /* Step+CoreDataClass.swift in Sources */, C473C2FC29A8DC4B0056B38A /* Date+Extensions.swift in Sources */, C4286EA32A1503320070D075 /* Stopwatch+CoreDataClass.swift in Sources */, C473C2F429A8DAE70056B38A /* Model+Extensions.swift in Sources */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 6863f2c..8f86ec9 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -29,64 +29,6 @@ enum CountdownState { case cancelled } -struct CountdownSequence: Codable { - var spans: [CountdownSpan] - - var currentSpan: CountdownSpan { - let now = Date() - let current: CountdownSpan? = self.spans.first { span in - return span.interval.start < now && span.interval.end > now - } - return current ?? self.spans.last ?? CountdownSequence.defaultSpan - } - - var currentEnd: Date { - return self.currentSpan.interval.end - } - - var dateInterval: DateInterval { - let firstSpan = self.spans.first ?? CountdownSequence.defaultSpan - let lastSpan = self.spans.last ?? CountdownSequence.defaultSpan - return DateInterval(start: firstSpan.start, end: lastSpan.end) - } - - var end: Date { - if let lastSpan = self.spans.last { - return lastSpan.end - } else { - fatalError("no spans") - } - } - - private static let defaultSpan = CountdownSpan(interval: DateInterval(start: Date(), end: Date()), name: "none", index: 0, loopCount: 1) -} - -struct CountdownSpan: Codable { - var interval: DateInterval - var name: String? - var index: Int16 - var loopCount: Int16 - - var start: Date { - return self.interval.start - } - - var end: Date { - return self.interval.end - } - - var label: String { - var components: [String] = [] - if let name { - components.append(name) - } - if loopCount > 1 { - components.append("#\(index + 1)") - } - return components.joined(separator: " ") - } -} - class Conductor: ObservableObject { static let maestro: Conductor = Conductor() @@ -98,7 +40,7 @@ class Conductor: ObservableObject { fileprivate var beats: Timer? = nil @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : CountdownSequence] - @UserDefault(PreferenceKey.pausedCountdowns.rawValue, defaultValue: [:]) static var savedPausedCountdowns: [String : TimeInterval] +// @UserDefault(PreferenceKey.pausedCountdowns.rawValue, defaultValue: [:]) static var savedPausedCountdowns: [String : Date] @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : LiveStopWatch] @Published private (set) var liveTimers: [LiveTimer] = [] @@ -108,7 +50,7 @@ class Conductor: ObservableObject { init() { self.currentCountdowns = Conductor.savedCountdowns self.currentStopwatches = Conductor.savedStopwatches - self.pausedCountdowns = Conductor.savedPausedCountdowns +// self.pausedCountdowns = Conductor.savedPausedCountdowns self.beats = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: { _ in self._cleanupCountdowns() @@ -128,14 +70,14 @@ class Conductor: ObservableObject { } } - @Published var pausedCountdowns: [String : TimeInterval] = [:] { - didSet { - Conductor.savedPausedCountdowns = pausedCountdowns - withAnimation { - self._buildLiveTimers() - } - } - } +// @Published var pausedCountdowns: [String : Date] = [:] { +// didSet { +// Conductor.savedPausedCountdowns = pausedCountdowns +// withAnimation { +// self._buildLiveTimers() +// } +// } +// } @Published var currentStopwatches: [String : LiveStopWatch] = [:] { didSet { @@ -183,7 +125,7 @@ class Conductor: ObservableObject { self.liveTimers.removeAll(where: { $0.id == id }) self.cancelledCountdowns.removeAll(where: { $0 == id }) self.currentStopwatches.removeValue(forKey: id) - self.pausedCountdowns.removeValue(forKey: id) +// self.pausedCountdowns.removeValue(forKey: id) if let soundPlayer = self._delayedSoundPlayers[id] { FileLogger.log("Stop sound player: \(self._timerName(id))") soundPlayer.stop() @@ -222,35 +164,18 @@ class Conductor: ObservableObject { do { - var totalDuration = 0.0 - var spans: [CountdownSpan] = [] - let now = Date() + let totalDuration = try self._schedulePlayers(countdown: countdown) - for i in 0.. TimeInterval { + + var totalDuration: TimeInterval = 0.0 + var spans: [CountdownStep] = [] + let now = Date() + + for i in 0.. Date { + fileprivate func _scheduleSoundPlayer(countdown: Countdown, step: Step, in interval: TimeInterval) throws -> Date { let start = Date() let end = start.addingTimeInterval(interval) // let countdownId = countdown.stringId - FileLogger.log("schedule countdown \(range.name ?? "''") at \(end)") + FileLogger.log("schedule countdown \(step.name ?? "''") at \(end)") - let sound = range.someSound ?? countdown.someSound ?? Sound.default + let sound = step.someSound ?? countdown.someSound ?? Sound.default - let idComponents = [countdown.stringId, range.stringId, interval.debugDescription] + let idComponents = [countdown.stringId, step.stringId, interval.debugDescription] let playerId = idComponents.joined(separator: idSeparator) let soundPlayer = try DelaySoundPlayer(sound: sound) self._delayedSoundPlayers[playerId] = soundPlayer @@ -299,7 +248,7 @@ class Conductor: ObservableObject { self.cancelSoundPlayer(id: id) self._recordAndRemoveCountdown(countdownId: id, cancel: true) - self.pausedCountdowns.removeValue(forKey: id) +// self.pausedCountdowns.removeValue(forKey: id) if Preferences.playCancellationSound { self._playCancellationSound() @@ -312,7 +261,7 @@ class Conductor: ObservableObject { let id = countdown.stringId if self.cancelledCountdowns.contains(id) { return .cancelled - } else if self.pausedCountdowns[id] != nil { + } else if self.currentCountdowns[id]?.pauseDate != nil { return .paused } else if let end = self.currentCountdowns[id]?.end, end > Date() { return .inprogress @@ -326,7 +275,10 @@ class Conductor: ObservableObject { // } func remainingPausedCountdownTime(_ countdown: Countdown) -> TimeInterval? { - return self.pausedCountdowns[countdown.stringId] + guard let sequence = self.currentCountdowns[countdown.stringId] else { + return nil + } + return sequence.remainingPausedCountdownTime() } func pauseCountdown(id: TimerID) { @@ -334,8 +286,11 @@ class Conductor: ObservableObject { return } - let remainingTime = sequence.currentSpan.end.timeIntervalSince(Date()) - self.pausedCountdowns[id] = remainingTime + Logger.log("Pause countdown") + sequence.pauseDate = Date() + +// let remainingTime = sequence.currentSpan.end.timeIntervalSince(Date()) +// self.pausedCountdowns[id] = Date() //remainingTime // cancel stuff self.cancelCurrentNotifications(countdownId: id) @@ -344,16 +299,38 @@ class Conductor: ObservableObject { } func resumeCountdown(id: TimerID) throws { + + let now = Date() + let context = PersistenceController.shared.container.viewContext if let countdown: Countdown = context.object(stringId: id), - let remainingTime = self.pausedCountdowns[id] { + let sequence = self.currentCountdowns[countdown.stringId], + let pauseDate = sequence.pauseDate { + +// let pauseDuration = now.timeIntervalSince(pauseDate) + +// var steps: [CountdownStep] = [] + for countdownStep in sequence.steps { + if countdownStep.end > pauseDate, let step = countdownStep.step(context: context) { + do { + let remainingTime = countdownStep.end.timeIntervalSince(pauseDate) + let _ = try self._scheduleSoundPlayer(countdown: countdown, step: step, in: remainingTime) + } catch { + Logger.error(error) + } + } +// steps.append(countdownStep.pauseAdjustedStep(duration: pauseDuration)) + } + sequence.resume() + +// self.currentCountdowns[countdown.stringId] = CountdownSequence(steps: steps) // TODO: RESUME // _ = try self._scheduleSoundPlayer(countdown: countdown, in: remainingTime) - self.pausedCountdowns.removeValue(forKey: id) } else { throw AppError.timerNotFound(id: id) } +// self.pausedCountdowns.removeValue(forKey: id) } fileprivate func _recordAndRemoveCountdown(countdownId: String, cancel: Bool) { @@ -407,23 +384,41 @@ class Conductor: ObservableObject { func restoreSoundPlayers() { - for (countdownId, span) in self.currentCountdowns { + for (countdownId, sequence) in self.currentCountdowns { if !self._delayedSoundPlayers.contains(where: { $0.key == countdownId }) { let context = PersistenceController.shared.container.viewContext if let countdown: Countdown = context.object(stringId: countdownId) { - do { - let sound: Sound = countdown.someSound ?? Sound.default - let soundPlayer = try DelaySoundPlayer(sound: sound) - self._delayedSoundPlayers[countdownId] = soundPlayer + let now = Date() + for countdownStep in sequence.steps { - // TODO: RESTORE -// try soundPlayer.restore(for: span.interval.end, repeatCount: Int(countdown.repeatCount)) - FileLogger.log("Restored sound player for \(self._timerName(countdownId))") - } catch { - Logger.error(error) + if let step = countdownStep.step(context: context) { + + let remainingTime = countdownStep.interval.end.timeIntervalSince(now) + if remainingTime > 0 { + do { + let _ = try self._scheduleSoundPlayer(countdown: countdown, step: step, in: remainingTime) + } catch { + Logger.error(error) + } + } + } } + + +// do { +// +// let sound: Sound = countdown.someSound ?? Sound.default +// let soundPlayer = try DelaySoundPlayer(sound: sound) +// self._delayedSoundPlayers[countdownId] = soundPlayer +// +// // TODO: RESTORE +// try soundPlayer.restore(for: sequence.interval.end, repeatCount: Int(countdown.repeatCount)) +// FileLogger.log("Restored sound player for \(self._timerName(countdownId))") +// } catch { +// Logger.error(error) +// } } } } @@ -446,7 +441,7 @@ class Conductor: ObservableObject { fileprivate func _cleanupCountdowns() { let now = Date() for (key, value) in self.currentCountdowns { - if value.end < now || self.cancelledCountdowns.contains(key) { + if (value.pauseDate == nil && value.end < now) || self.cancelledCountdowns.contains(key) { self._recordAndRemoveCountdown(countdownId: key, cancel: false) } } @@ -562,17 +557,21 @@ class Conductor: ObservableObject { } } + fileprivate func _soundPlayers(id: TimerID) -> [TimerID : DelaySoundPlayer] { + return self._delayedSoundPlayers.filter { (key, value) in + key.starts(with: id) + } + } + func cancelSoundPlayer(id: TimerID) { - let keys = self._delayedSoundPlayers.keys.filter { $0.starts(with: id) } - - for key in keys { - if let soundPlayer = self._delayedSoundPlayers[key] { - soundPlayer.stop() - self._delayedSoundPlayers.removeValue(forKey: key) - } + let players = self._soundPlayers(id: id) + for (key, player) in players { + player.stop() + self._delayedSoundPlayers.removeValue(forKey: key) } - FileLogger.log("cancelled \(keys.count) sound players for \(self._timerName(id))") + + FileLogger.log("cancelled \(players.count) sound players for \(self._timerName(id))") self.deactivateAudioSessionIfPossible() } diff --git a/LeCountdown/CountdownSequence.swift b/LeCountdown/CountdownSequence.swift new file mode 100644 index 0000000..a10188f --- /dev/null +++ b/LeCountdown/CountdownSequence.swift @@ -0,0 +1,118 @@ +// +// CountdownSequence.swift +// LeCountdown +// +// Created by Laurent Morvillier on 30/11/2023. +// + +import Foundation +import CoreData + +class CountdownSequence: Codable { + var steps: [CountdownStep] + var pauseDate: Date? = nil + + init(steps: [CountdownStep], pauseDate: Date? = nil) { + self.steps = steps + self.pauseDate = pauseDate + } + + var currentSpan: CountdownStep { + let referenceDate = self.pauseDate ?? Date() + let current: CountdownStep? = self.steps.first { span in + return span.interval.start < referenceDate && span.interval.end > referenceDate + } + return current ?? self.steps.last ?? CountdownSequence.defaultSpan + } + + var currentEnd: Date { + return self.currentSpan.interval.end + } + + var dateInterval: DateInterval { + let firstSpan = self.steps.first ?? CountdownSequence.defaultSpan + let lastSpan = self.steps.last ?? CountdownSequence.defaultSpan + return DateInterval(start: firstSpan.start, end: lastSpan.end) + } + + var end: Date { + if let lastSpan = self.steps.last { + return lastSpan.end + } else { + fatalError("no spans") + } + } + + func remainingPausedCountdownTime() -> TimeInterval? { + if let pauseDate = self.pauseDate, + let currentEnd = self._endOfStep(for: pauseDate) { + return currentEnd.timeIntervalSince(pauseDate) + } + return nil + } + + fileprivate func _endOfStep(for date: Date) -> Date? { + let step = self.steps.first { step in + return date > step.start && date < step.end + } + return step?.end + } + + func resume() { + guard let pauseDate = self.pauseDate else { return } + let pauseDuration = Date().timeIntervalSince(pauseDate) + for step in self.steps { + step.pauseAdjustedStep(duration: pauseDuration) + } + self.pauseDate = nil + } + + private static let defaultSpan = CountdownStep(interval: DateInterval(start: Date(), end: Date()), name: "none", index: 0, loopCount: 1, stepId: "") + +} + +class CountdownStep: Codable { + var interval: DateInterval + var name: String? + var index: Int16 + var loopCount: Int16 + var stepId: String + + init(interval: DateInterval, name: String? = nil, index: Int16, loopCount: Int16, stepId: String) { + self.interval = interval + self.name = name + self.index = index + self.loopCount = loopCount + self.stepId = stepId + } + + var start: Date { + return self.interval.start + } + + var end: Date { + return self.interval.end + } + + var label: String { + var components: [String] = [] + if let name { + components.append(name) + } + if loopCount > 1 { + components.append("#\(index + 1)") + } + return components.joined(separator: " ") + } + + func step(context: NSManagedObjectContext) -> Step? { + return context.object(stringId: self.stepId) + } + + func pauseAdjustedStep(duration: TimeInterval) { + let start = self.interval.start.addingTimeInterval(duration) + let end = self.interval.end.addingTimeInterval(duration) + self.interval = DateInterval(start: start, end: end) + } + +} diff --git a/LeCountdown/Model/Fakes.swift b/LeCountdown/Model/Fakes.swift index 0835f2c..99c3847 100644 --- a/LeCountdown/Model/Fakes.swift +++ b/LeCountdown/Model/Fakes.swift @@ -13,9 +13,10 @@ extension Countdown { static func fake(context: NSManagedObjectContext) -> Countdown { let cd = Countdown(context: context) - let timeRange = TimeRange(context: context) - timeRange.duration = 5.0 - timeRange.name = "Infusion" + let step = Step(context: context) + step.duration = 5.0 + step.name = "Infusion" + cd.addToSteps(step) let activity = Activity(context: context) activity.name = "Tea" diff --git a/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift b/LeCountdown/Model/Generation/Countdown+CoreDataProperties.swift index b706ded..027dd0a 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 29/11/2023. +// Created by Laurent Morvillier on 30/11/2023. // // @@ -18,23 +18,23 @@ extension Countdown { @NSManaged public var loops: Int16 @NSManaged public var duration: Double - @NSManaged public var timeRanges: NSSet? + @NSManaged public var steps: NSSet? } -// MARK: Generated accessors for timeRanges +// MARK: Generated accessors for steps extension Countdown { - @objc(addTimeRangesObject:) - @NSManaged public func addToTimeRanges(_ value: TimeRange) + @objc(addStepsObject:) + @NSManaged public func addToSteps(_ value: Step) - @objc(removeTimeRangesObject:) - @NSManaged public func removeFromTimeRanges(_ value: TimeRange) + @objc(removeStepsObject:) + @NSManaged public func removeFromSteps(_ value: Step) - @objc(addTimeRanges:) - @NSManaged public func addToTimeRanges(_ values: NSSet) + @objc(addSteps:) + @NSManaged public func addToSteps(_ values: NSSet) - @objc(removeTimeRanges:) - @NSManaged public func removeFromTimeRanges(_ values: NSSet) + @objc(removeSteps:) + @NSManaged public func removeFromSteps(_ values: NSSet) } diff --git a/LeCountdown/Model/Generation/Step+CoreDataClass.swift b/LeCountdown/Model/Generation/Step+CoreDataClass.swift new file mode 100644 index 0000000..55e896b --- /dev/null +++ b/LeCountdown/Model/Generation/Step+CoreDataClass.swift @@ -0,0 +1,15 @@ +// +// Step+CoreDataClass.swift +// LeCountdown +// +// Created by Laurent Morvillier on 30/11/2023. +// +// + +import Foundation +import CoreData + +@objc(Step) +public class Step: NSManagedObject { + +} diff --git a/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift b/LeCountdown/Model/Generation/Step+CoreDataProperties.swift similarity index 61% rename from LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift rename to LeCountdown/Model/Generation/Step+CoreDataProperties.swift index 6b3a18c..15936e2 100644 --- a/LeCountdown/Model/Generation/TimeRange+CoreDataProperties.swift +++ b/LeCountdown/Model/Generation/Step+CoreDataProperties.swift @@ -1,8 +1,8 @@ // -// TimeRange+CoreDataProperties.swift +// Step+CoreDataProperties.swift // LeCountdown // -// Created by Laurent Morvillier on 23/11/2023. +// Created by Laurent Morvillier on 30/11/2023. // // @@ -10,10 +10,10 @@ import Foundation import CoreData -extension TimeRange { +extension Step { - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "TimeRange") + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Step") } @NSManaged public var duration: Double @@ -24,6 +24,6 @@ extension TimeRange { } -extension TimeRange : Identifiable { +extension Step : Identifiable { } diff --git a/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift b/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift deleted file mode 100644 index 25cd2cb..0000000 --- a/LeCountdown/Model/Generation/TimeRange+CoreDataClass.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// TimeRange+CoreDataClass.swift -// LeCountdown -// -// Created by Laurent Morvillier on 22/11/2023. -// -// - -import Foundation -import CoreData - -@objc(TimeRange) -public class TimeRange: NSManagedObject { - -} 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 0e3e888..fef2ebc 100644 --- a/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents +++ b/LeCountdown/Model/LeCountdown.xcdatamodeld/LeCountdown.0.6.6.xcdatamodel/contents @@ -20,7 +20,7 @@ - + @@ -35,16 +35,16 @@ - - - - - - + - + + + + + + \ No newline at end of file diff --git a/LeCountdown/Model/Model+Extensions.swift b/LeCountdown/Model/Model+Extensions.swift index 9c1bfc6..43e0934 100644 --- a/LeCountdown/Model/Model+Extensions.swift +++ b/LeCountdown/Model/Model+Extensions.swift @@ -188,13 +188,13 @@ extension CustomSound : Localized { } -extension TimeRange: StoresSound { +extension Step: StoresSound { - static func fake(context: NSManagedObjectContext) -> TimeRange { - let timeRange = TimeRange(context: context) - timeRange.duration = 30.0 - timeRange.name = "Pause" - return timeRange + static func fake(context: NSManagedObjectContext) -> Step { + let step = Step(context: context) + step.duration = 30.0 + step.name = "Pause" + return step } } diff --git a/LeCountdown/Model/Model+SharedExtensions.swift b/LeCountdown/Model/Model+SharedExtensions.swift index 575a4fb..0731d13 100644 --- a/LeCountdown/Model/Model+SharedExtensions.swift +++ b/LeCountdown/Model/Model+SharedExtensions.swift @@ -46,32 +46,32 @@ extension AbstractTimer { extension Countdown { - func sortedRanges() -> [TimeRange] { - guard let ranges = self.timeRanges as? Set else { + func sortedSteps() -> [Step] { + guard let steps = self.steps as? Set else { return [] } - return ranges.sorted(using: SortDescriptor(\TimeRange.order, order: .forward)) + return steps.sorted(using: SortDescriptor(\Step.order, order: .forward)) } override var defaultName: String { return NSLocalizedString("Countdown", comment: "") } - var rangeCount: Int { - return self.timeRanges?.count ?? 0 + var stepCount: Int { + return self.steps?.count ?? 0 } var formattedDuration: String { - let durations: [String] = self.sortedRanges().map { $0.duration.hourMinuteSecond } + let durations: [String] = self.sortedSteps().map { $0.duration.hourMinuteSecond } var formatted: String if durations.count > 1 { - formatted = durations.joined(separator: " / ") + formatted = "\(durations.count) \(NSLocalizedString("Steps", comment: ""))" } else { formatted = durations.first ?? "none" } if self.loops > 1 { - return "\(formatted) * \(loops)" + return "\(loops) * \(formatted)" } else { return formatted } diff --git a/LeCountdown/Patcher.swift b/LeCountdown/Patcher.swift index cc6fc63..3c042c8 100644 --- a/LeCountdown/Patcher.swift +++ b/LeCountdown/Patcher.swift @@ -23,10 +23,10 @@ class Patcher { let context = PersistenceController.shared.container.viewContext for patch in Patch.allCases { - if true { +// if true { + if !UserDefaults.standard.bool(forKey: patch.key) { Logger.log("PATCH!!!") -// if !UserDefaults.standard.bool(forKey: patch.key) { - + do { switch patch { case .interval: @@ -48,12 +48,12 @@ class Patcher { let countdowns: [Countdown] = try context.fetch(Countdown.fetchRequest()) for countdown in countdowns { - if let ranges = countdown.timeRanges, ranges.count == 0 { - let range = TimeRange(context: context) - range.duration = countdown.duration - range.name = countdown.name - range.order = 0 - countdown.addToTimeRanges(range) + if let steps = countdown.steps, steps.count == 0 { + let step = Step(context: context) + step.duration = countdown.duration + step.name = countdown.name + step.order = 0 + countdown.addToSteps(step) } } diff --git a/LeCountdown/Sound/DelaySoundPlayer.swift b/LeCountdown/Sound/DelaySoundPlayer.swift index 3090f92..ff25140 100644 --- a/LeCountdown/Sound/DelaySoundPlayer.swift +++ b/LeCountdown/Sound/DelaySoundPlayer.swift @@ -46,7 +46,7 @@ import AVFoundation self._player.delegate = self // self._player.numberOfLoops = repeatCount - Logger.log("self._player.deviceCurrentTime = \(self._player.deviceCurrentTime)") +// Logger.log("self._player.deviceCurrentTime = \(self._player.deviceCurrentTime)") let time: TimeInterval = self._player.deviceCurrentTime + duration let result = self._player.play(atTime: time) FileLogger.log("play \(String(describing: self._player.url)) >atTime: \(time.timeFormatted), result = \(result), isMainThread = \(Thread.isMainThread)") diff --git a/LeCountdown/Views/Countdown/CountdownDialView.swift b/LeCountdown/Views/Countdown/CountdownDialView.swift index 57ee46f..9272535 100644 --- a/LeCountdown/Views/Countdown/CountdownDialView.swift +++ b/LeCountdown/Views/Countdown/CountdownDialView.swift @@ -29,6 +29,7 @@ struct CountdownDialView: View, DialStyle { Text(countdown.formattedDuration) .fontWeight(.semibold) .foregroundColor(self._durationColor) + .multilineTextAlignment(.leading) } Spacer() }.font(.system(size: self.dialFontSize)) diff --git a/LeCountdown/Views/Countdown/CountdownFormView.swift b/LeCountdown/Views/Countdown/CountdownFormView.swift index 39c0220..3f95835 100644 --- a/LeCountdown/Views/Countdown/CountdownFormView.swift +++ b/LeCountdown/Views/Countdown/CountdownFormView.swift @@ -29,7 +29,7 @@ struct CountdownFormView : View { @State var showRangeSheet = false - @State var selectedRange: TimeRange? = nil + @State var selectedStepItem: StepItem? = nil var body: some View { @@ -46,14 +46,14 @@ struct CountdownFormView : View { } Section { - ForEach(self.model.ranges) { range in + ForEach(self.model.stepItems) { stepItem in Button { - self.selectedRange = range + self.selectedStepItem = stepItem } label: { - LabeledContent(range.name ?? "", value: range.duration.hourMinuteSecond) + LabeledContent(stepItem.name ?? "", value: stepItem.duration.hourMinuteSecond) } }.onDelete { indexSet in - self.model.deleteRange(indexSet) + self.model.deleteStep(indexSet: indexSet) } } @@ -98,16 +98,16 @@ struct CountdownFormView : View { SoundFormView(model: self.model) } - .sheet(item: self.$selectedRange, onDismiss: { + .sheet(item: self.$selectedStepItem, onDismiss: { self.model.objectWillChange.send() }) { item in - RangeFormView(timeRange: item) + RangeFormView(stepItem: item) } } fileprivate func _addInterval() { - self.selectedRange = self.model.addInterval(context: self.viewContext) + self.selectedStepItem = self.model.addStepItem() } func duration() -> TimeInterval { diff --git a/LeCountdown/Views/Countdown/NewCountdownView.swift b/LeCountdown/Views/Countdown/NewCountdownView.swift index b53b6b0..947273f 100644 --- a/LeCountdown/Views/Countdown/NewCountdownView.swift +++ b/LeCountdown/Views/Countdown/NewCountdownView.swift @@ -79,7 +79,7 @@ struct CountdownEditView : View { init(isPresented: Binding, countdown: Countdown) { _isPresented = isPresented self.countdown = countdown - self.hasRanges = countdown.rangeCount > 1 + self.hasRanges = countdown.stepCount > 1 } init(isPresented: Binding, preset: Preset) { @@ -195,16 +195,16 @@ struct CountdownEditView : View { self.nameString = preset.localizedName self.duration = preset.duration - self.model.ranges = preset.ranges(context: self.viewContext) + self.model.stepItems = preset.stepItems() self.model.soundModel.loadPreset(preset) } fileprivate func _loadCountdown(_ countdown: Countdown) { - let ranges = countdown.sortedRanges() - self.model.ranges = ranges - if let range = ranges.first { + let steps: [Step] = countdown.sortedSteps() + self.model.stepItems = steps.map { $0.item } + if let range = steps.first { self.duration = range.duration self.nameString = range.name ?? "" } @@ -234,8 +234,6 @@ struct CountdownEditView : View { cd = Countdown(context: viewContext) } -// cd.duration = self.duration - if self._isNewCountdown { let max: Int16 do { @@ -257,22 +255,24 @@ struct CountdownEditView : View { cd.setConfirmationSounds(self.model.confirmationSoundModel.sounds) cd.loops = self.model.loops - if self.model.ranges.count > 0 { - if let timeRanges = cd.timeRanges { - cd.removeFromTimeRanges(timeRanges) - } - - for (index, range) in self.model.ranges.enumerated() { - range.order = Int16(index) - range.duration = range.duration - range.name = range.name - cd.addToTimeRanges(range) + for step in cd.sortedSteps() { + viewContext.delete(step) + } + + if self.model.stepItems.count > 0 { + for (index, stepItem) in self.model.stepItems.enumerated() { + + let step = stepItem.step(context: viewContext) + step.order = Int16(index) + step.duration = stepItem.duration + step.name = stepItem.name + cd.addToSteps(step) } } else { - let timeRange = TimeRange(context: viewContext) - timeRange.duration = self.duration - timeRange.name = self.nameString - cd.addToTimeRanges(timeRange) + let step = Step(context: viewContext) + step.duration = self.duration + step.name = self.nameString + cd.addToSteps(step) } if !self.nameString.isEmpty { @@ -321,6 +321,14 @@ struct CountdownEditView : View { } +fileprivate extension Step { + + var item: StepItem { + return StepItem(name: self.name, duration: self.duration) + } + +} + struct NewCountdownView_Previews: PreviewProvider { static var previews: some View { NewCountdownView(isPresented: .constant(true), hasRanges: false) diff --git a/LeCountdown/Views/Countdown/RangeFormView.swift b/LeCountdown/Views/Countdown/RangeFormView.swift index 0f8d0b9..3f14a24 100644 --- a/LeCountdown/Views/Countdown/RangeFormView.swift +++ b/LeCountdown/Views/Countdown/RangeFormView.swift @@ -11,7 +11,7 @@ struct RangeFormView: View { @Environment(\.dismiss) private var dismiss - @ObservedObject var timeRange: TimeRange + @ObservedObject var stepItem: StepItem @State var namePlaceholder = "name" @State var name: String = "" @@ -43,20 +43,20 @@ struct RangeFormView: View { } } }.onAppear { - if let name = self.timeRange.name, !name.isEmpty { + if let name = self.stepItem.name, !name.isEmpty { self.namePlaceholder = name } - self.duration = self.timeRange.duration + self.duration = self.stepItem.duration } } fileprivate func _doneHandler() { if self.name.isEmpty { - self.timeRange.name = self.namePlaceholder + self.stepItem.name = self.namePlaceholder } else { - self.timeRange.name = self.name + self.stepItem.name = self.name } - self.timeRange.duration = self.duration + self.stepItem.duration = self.duration self.dismiss() } diff --git a/LeCountdown/Views/LiveTimerListView.swift b/LeCountdown/Views/LiveTimerListView.swift index c2cc9cf..3696a1c 100644 --- a/LeCountdown/Views/LiveTimerListView.swift +++ b/LeCountdown/Views/LiveTimerListView.swift @@ -202,7 +202,7 @@ struct LiveCountdownView: View { TimeView(text: NSLocalizedString("Cancelled", comment: "")) } - var name = self._nameForState(state: state) + let name = self._nameForState(state: state) Text(name.uppercased()) .foregroundColor(self.colorScheme == .dark ? .white : .black) diff --git a/LeCountdown/Views/PresetsView.swift b/LeCountdown/Views/PresetsView.swift index a736f85..dd434cc 100644 --- a/LeCountdown/Views/PresetsView.swift +++ b/LeCountdown/Views/PresetsView.swift @@ -289,15 +289,14 @@ enum Preset: Int, Identifiable, CaseIterable { return CountdownIntervalGroup(repeatCount: 0, intervals: [CountdownInterval(duration: self.duration)]) } - func ranges(context: NSManagedObjectContext) -> [TimeRange] { + func stepItems() -> [StepItem] { switch self { case .runningSplits: return [] default: - let timeRange = TimeRange(context: context) - timeRange.name = self.localizedName - timeRange.duration = self.duration - return [timeRange] + let step = StepItem(name: self.localizedName) + step.duration = self.duration + return [step] } } diff --git a/LeCountdown/Views/StartView.swift b/LeCountdown/Views/StartView.swift index 1a6f1b3..d44099e 100644 --- a/LeCountdown/Views/StartView.swift +++ b/LeCountdown/Views/StartView.swift @@ -152,9 +152,9 @@ class Customization: ObservableObject { let countdown = Countdown(context: context) countdown.activity = CoreDataRequests.getOrCreateActivity(name: preset.localizedName) - for range in preset.ranges(context: context) { - range.duration = self.duration - countdown.addToTimeRanges(range) + for stepItem in preset.stepItems() { + stepItem.duration = self.duration + countdown.addToSteps(stepItem.step(context: context)) } countdown.playableIds = self.timerModel.soundModel.playableIds diff --git a/LeCountdown/Views/TimerModel.swift b/LeCountdown/Views/TimerModel.swift index f2f2f33..17778c8 100644 --- a/LeCountdown/Views/TimerModel.swift +++ b/LeCountdown/Views/TimerModel.swift @@ -15,30 +15,44 @@ protocol SoundHolder { func selectPlaylist(_ playlist: Playlist, selected: Bool) } -//struct StageItem { -// var name: String? -// var duration: TimeInterval -//} +class StepItem: Identifiable, ObservableObject { + let id: String = UUID().uuidString + var name: String? = nil + var duration: TimeInterval = 0.0 + + init(name: String? = nil, duration: TimeInterval = 0.0) { + self.name = name + self.duration = duration + } + + func step(context: NSManagedObjectContext) -> Step { + let step = Step(context: context) + step.name = self.name + step.duration = self.duration + return step + } + +} class TimerModel: ObservableObject { @Published var soundModel: SoundModel = SoundModel() @Published var confirmationSoundModel: SoundModel = SoundModel() - @Published var ranges: [TimeRange] = [] + @Published var stepItems: [StepItem] = [] @Published var loops: Int16 = 1 - func addInterval(context: NSManagedObjectContext) -> TimeRange { - let timeRange = TimeRange(context: context) - let index: String = (self.ranges.count + 1).formatted() - timeRange.name = NSLocalizedString("Step", comment: "") + " " + index + func addStepItem() -> StepItem { + let index: String = (self.stepItems.count + 1).formatted() + let name = NSLocalizedString("Step", comment: "") + " " + index + let step = StepItem(name: name) - self.ranges.append(timeRange) - return timeRange + self.stepItems.append(step) + return step } - func deleteRange(indexSet: IndexSet, context: NSManagedObjectContext) { - self.ranges.remove(atOffsets: indexSet) + func deleteStep(indexSet: IndexSet) { + self.stepItems.remove(atOffsets: indexSet) } } diff --git a/LeCountdown/en.lproj/Localizable.strings b/LeCountdown/en.lproj/Localizable.strings index f224eb1..a067a89 100644 --- a/LeCountdown/en.lproj/Localizable.strings +++ b/LeCountdown/en.lproj/Localizable.strings @@ -4,6 +4,7 @@ "Play confirmation sound" = "Play sound on start"; "Create your first timer or stopwatch!" = "Create your first timer or stopwatch!"; "Step" = "Step"; +"Steps" = "steps"; "Create an advanced timer" = "Create an advanced timer"; "Steps & repeat" = "Steps & repeat"; -"Add interval" = "Add interval"; +"Add interval" = "Add step"; diff --git a/LeCountdown/fr.lproj/Localizable.strings b/LeCountdown/fr.lproj/Localizable.strings index 7b2be1d..e5c523a 100644 --- a/LeCountdown/fr.lproj/Localizable.strings +++ b/LeCountdown/fr.lproj/Localizable.strings @@ -293,6 +293,7 @@ "Write a review!" = "Écrivez un avis !"; "Adjust volume on launch" = "Ajuster le volume au lancement"; "Step" = "Étape"; +"Steps" = "étapes"; "Create an advanced timer" = "Créer un minuteur avancé"; "Steps & repeat" = "Intervalles et répétitions"; -"Add interval" = "Ajouter un intervalle"; +"Add interval" = "Ajouter une étape"; diff --git a/TimeRange+CoreDataClass.swift b/TimeRange+CoreDataClass.swift deleted file mode 100644 index facd656..0000000 --- a/TimeRange+CoreDataClass.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// TimeRange+CoreDataClass.swift -// LeCountdown -// -// Created by Laurent Morvillier on 23/11/2023. -// -// - -import Foundation -import CoreData - -@objc(TimeRange) -public class TimeRange: NSManagedObject { - -} diff --git a/TimeRange+CoreDataProperties.swift b/TimeRange+CoreDataProperties.swift deleted file mode 100644 index 26c41bd..0000000 --- a/TimeRange+CoreDataProperties.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// TimeRange+CoreDataProperties.swift -// LeCountdown -// -// Created by Laurent Morvillier on 23/11/2023. -// -// - -import Foundation -import CoreData - - -extension TimeRange { - - @nonobjc public class func fetchRequest() -> NSFetchRequest { - return NSFetchRequest(entityName: "TimeRange") - } - - @NSManaged public var duration: Double - @NSManaged public var name: String? - @NSManaged public var order: Int16 - @NSManaged public var soundList: String? - @NSManaged public var countdown: Countdown? - -} - -extension TimeRange : Identifiable { - -}