From 4c4607ecc8e940a1740fea25cd437f624f5a90db Mon Sep 17 00:00:00 2001 From: Laurent Date: Thu, 6 Apr 2023 12:33:41 +0200 Subject: [PATCH] Fixes stopwatches --- LeCountdown.xcodeproj/project.pbxproj | 6 ++++ LeCountdown/Conductor.swift | 36 ++++++++++++--------- LeCountdown/Model/LiveStopWatch.swift | 38 ++++++++++++++++++++++ LeCountdown/TimerRouter.swift | 2 +- LeCountdown/Views/LiveTimerListView.swift | 39 ++++++++++------------- 5 files changed, 82 insertions(+), 39 deletions(-) create mode 100644 LeCountdown/Model/LiveStopWatch.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 5e6ceed..208c0d7 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -108,6 +108,8 @@ C498E5A3298D720600E90DE0 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A2298D720600E90DE0 /* TestView.swift */; }; C498E5A5299152B400E90DE0 /* GreenCheckmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */; }; C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */; }; + C49C346929DECA4400AAC6FC /* LiveStopWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C346829DECA4400AAC6FC /* LiveStopWatch.swift */; }; + C49C346A29DECC7100AAC6FC /* LiveStopWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C346829DECA4400AAC6FC /* LiveStopWatch.swift */; }; C4A16D8C29C4A5BA00143D5E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */; }; C4A16D8D29C4A5BA00143D5E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */; }; C4A16D8E29C4A5BA00143D5E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */; }; @@ -395,6 +397,7 @@ C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = ""; }; C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = ""; }; C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GreenCheckmarkView.swift; sourceTree = ""; }; + C49C346829DECA4400AAC6FC /* LiveStopWatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveStopWatch.swift; sourceTree = ""; }; C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; C4A16D9429C4B06400143D5E /* StatePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatePlayer.swift; sourceTree = ""; }; C4A16D9A29D0A7D300143D5E /* MRKRSTPHR_synth_one_shot_bleep_G.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = MRKRSTPHR_synth_one_shot_bleep_G.wav; sourceTree = ""; }; @@ -701,6 +704,7 @@ C4F8B188298AC248005C86A5 /* Generation */, C4060DCA297AE73D003FAB80 /* LeCountdown.xcdatamodeld */, C438C80C2982847300BF3EF9 /* CoreDataRequests.swift */, + C49C346829DECA4400AAC6FC /* LiveStopWatch.swift */, C498E5A0298D543900E90DE0 /* LiveTimer.swift */, C438C806298195E600BF3EF9 /* Model+Extensions.swift */, C4BA2B39299F838000CB4FBA /* Model+SharedExtensions.swift */, @@ -1219,6 +1223,7 @@ C473C33C29ACEC4F0056B38A /* Tip.swift in Sources */, C498E5A3298D720600E90DE0 /* TestView.swift in Sources */, C4BA2B06299A8F8D00CB4FBA /* PresetsView.swift in Sources */, + C49C346929DECA4400AAC6FC /* LiveStopWatch.swift in Sources */, C473C33929ACDBD70056B38A /* TipView.swift in Sources */, C445FA8F2987B83B0054D761 /* SoundPlayer.swift in Sources */, C4F8B1A7298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, @@ -1343,6 +1348,7 @@ buildActionMask = 2147483647; files = ( C473C2FD29A8DC690056B38A /* CoreDataRequests.swift in Sources */, + C49C346A29DECC7100AAC6FC /* LiveStopWatch.swift in Sources */, C473C2F329A8DA6F0056B38A /* LiveTimer.swift in Sources */, C4F8B1C6298ACC1F005C86A5 /* SoundPlayer.swift in Sources */, C4F8B1A2298AC288005C86A5 /* Record+CoreDataProperties.swift in Sources */, diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index f132de0..211da46 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -31,7 +31,7 @@ class Conductor: ObservableObject { fileprivate var _delayedSoundPlayers: [TimerID : DelaySoundPlayer] = [:] @UserDefault(PreferenceKey.countdowns.rawValue, defaultValue: [:]) static var savedCountdowns: [String : DateInterval] - @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : Date] + @UserDefault(PreferenceKey.stopwatches.rawValue, defaultValue: [:]) static var savedStopwatches: [String : LiveStopWatch] @Published private (set) var liveTimers: [LiveTimer] = [] @@ -52,7 +52,7 @@ class Conductor: ObservableObject { } } - @Published var currentStopwatches: [String : Date] = [:] { + @Published var currentStopwatches: [String : LiveStopWatch] = [:] { didSet { Conductor.savedStopwatches = currentStopwatches withAnimation { @@ -92,8 +92,8 @@ class Conductor: ObservableObject { } } - let liveStopwatches = self.currentStopwatches.map { - return LiveTimer(id: $0, date: $1) + let liveStopwatches: [LiveTimer] = self.currentStopwatches.map { + return LiveTimer(id: $0, date: $1.start) } for liveStopwatch in liveStopwatches { if let index = self.liveTimers.firstIndex(where: { $0.id == liveStopwatch.id }) { @@ -180,29 +180,35 @@ class Conductor: ObservableObject { func startStopwatch(_ stopwatch: Stopwatch) { DispatchQueue.main.async { - let now = Date() - Conductor.maestro.currentStopwatches[stopwatch.stringId] = now + let lsw = LiveStopWatch(start: Date()) + Conductor.maestro.currentStopwatches[stopwatch.stringId] = lsw if Preferences.playConfirmationSound { self._playSound(Const.confirmationSound.rawValue) } - self._launchLiveActivity(stopwatch: stopwatch, start: now) + self._launchLiveActivity(stopwatch: stopwatch, start: lsw.start) // self._createTimerIntent(stopwatch) } } - 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: stopwatch, dateInterval: DateInterval(start: start, end: end ?? Date())) - } catch { - Logger.error(error) + func stopStopwatch(_ stopwatch: Stopwatch) { + if let lsw = Conductor.maestro.currentStopwatches[stopwatch.stringId] { + + if lsw.end == nil { + let end = Date() + lsw.end = end + // Conductor.maestro.currentStopwatches.removeValue(forKey: stopwatch.stringId) + do { + try CoreDataRequests.recordActivity(timer: stopwatch, dateInterval: DateInterval(start: lsw.start, end: end)) + } catch { + Logger.error(error) + } + self._endLiveActivity(timerId: stopwatch.stringId) } - self._endLiveActivity(timerId: stopwatch.stringId) + } } diff --git a/LeCountdown/Model/LiveStopWatch.swift b/LeCountdown/Model/LiveStopWatch.swift new file mode 100644 index 0000000..95c6395 --- /dev/null +++ b/LeCountdown/Model/LiveStopWatch.swift @@ -0,0 +1,38 @@ +// +// LiveStopWatch.swift +// LeCountdown +// +// Created by Laurent Morvillier on 06/04/2023. +// + +import Foundation + +class LiveStopWatch: Codable, Hashable, Equatable { + + var start: Date + var end: Date? + + init(start: Date, end: Date? = nil) { + self.start = start + self.end = end + } + + static func == (lhs: LiveStopWatch, rhs: LiveStopWatch) -> Bool { + if lhs.start == rhs.start { + if let end = lhs.end, end == rhs.end { + return true + } else if lhs.end == nil && rhs.end == nil { + return true + } + } + return false + } + + func hash(into hasher: inout Hasher) { + hasher.combine(start) + if let end { + hasher.combine(end) + } + } + +} diff --git a/LeCountdown/TimerRouter.swift b/LeCountdown/TimerRouter.swift index 335ea18..ca03673 100644 --- a/LeCountdown/TimerRouter.swift +++ b/LeCountdown/TimerRouter.swift @@ -71,7 +71,7 @@ class TimerRouter { handler(.success(Void())) } - fileprivate static func _stopStopwatch(_ stopwatch: Stopwatch, end: Date? = nil) { + fileprivate static func _stopStopwatch(_ stopwatch: Stopwatch) { Conductor.maestro.stopStopwatch(stopwatch) } diff --git a/LeCountdown/Views/LiveTimerListView.swift b/LeCountdown/Views/LiveTimerListView.swift index fe69279..94da0ec 100644 --- a/LeCountdown/Views/LiveTimerListView.swift +++ b/LeCountdown/Views/LiveTimerListView.swift @@ -7,20 +7,6 @@ import SwiftUI -class LiveStopwatchModel: ObservableObject { - - @Published var endDate: Date? = nil - - func stop(_ stopwatch: Stopwatch) { - - let now = Date() - self.endDate = now - - Conductor.maestro.stopStopwatch(stopwatch) - - } -} - fileprivate let liveViewSize: CGFloat = 70.0 fileprivate let timerFontSize: CGFloat = 32.0 fileprivate let actionButtonFontSize: CGFloat = 36.0 @@ -42,25 +28,27 @@ struct LiveStopwatchView: View { @Environment(\.managedObjectContext) private var viewContext @EnvironmentObject var conductor: Conductor - @StateObject var model: LiveStopwatchModel = LiveStopwatchModel() - @State var stopwatch: Stopwatch + @State var stopped: Bool = false + var date: Date + var endDate: Date? { + return self.conductor.currentStopwatches[stopwatch.stringId]?.end + } + var body: some View { - let running = (self.model.endDate == nil) - HStack { VStack(alignment: .leading) { - if running { + if !self.stopped { TimelineView(.periodic(from: self.date, by: 0.01)) { context in TimeView(text: self._formattedDuration(date: context.date)) } } else { - let duration = self.model.endDate?.timeIntervalSince(self.date) ?? 0.0 + let duration = self.endDate?.timeIntervalSince(self.date) ?? 0.0 TimeView(text: duration.hourMinuteSecondHS) } Text(stopwatch.displayName.uppercased()) @@ -69,7 +57,7 @@ struct LiveStopwatchView: View { Spacer() Group { - if !running { + if self.stopped { GreenCheckmarkView() } else { Image(systemName: "stop.circle.fill") @@ -88,8 +76,8 @@ struct LiveStopwatchView: View { fileprivate func _actionHandler() { withAnimation { - if self.model.endDate == nil { - self.model.stop(stopwatch) + if self.endDate == nil { + self._stop() } else { self._dismiss() } @@ -104,6 +92,11 @@ struct LiveStopwatchView: View { let duration = date.timeIntervalSince(self.date) return duration.hourMinuteSecondHS } + + fileprivate func _stop() { + self.stopped = true + Conductor.maestro.stopStopwatch(self.stopwatch) + } } struct LiveCountdownView: View {