From 035361f4596c14b65bb58adbe333e6774c9689a3 Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 3 Feb 2023 20:40:36 +0100 Subject: [PATCH] Fixes timer refresh --- LeCountdown.xcodeproj/project.pbxproj | 12 +- LeCountdown/AppDelegate.swift | 5 +- LeCountdown/Conductor.swift | 28 ++-- LeCountdown/CountdownScheduler.swift | 8 +- LeCountdown/TimerRouter.swift | 2 +- .../Utils/TimeInterval+Extensions.swift | 11 ++ LeCountdown/Views/ContentView.swift | 9 +- .../Views/Countdown/CountdownDialView.swift | 19 +-- LeCountdown/Views/LiveTimerListView.swift | 123 ++++++++++++++++++ LeCountdown/Views/LiveTimerView.swift | 70 ---------- .../Views/Stopwatch/StopwatchDialView.swift | 5 - LeCountdown/Views/TestView.swift | 40 ++++++ 12 files changed, 214 insertions(+), 118 deletions(-) create mode 100644 LeCountdown/Views/LiveTimerListView.swift delete mode 100644 LeCountdown/Views/LiveTimerView.swift create mode 100644 LeCountdown/Views/TestView.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index 5cd6c1b..f81a4d5 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -62,8 +62,9 @@ C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B58298411E800D5D950 /* CountdownFormView.swift */; }; C4742B5B298414B000D5D950 /* ImageSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5A298414B000D5D950 /* ImageSelectionView.swift */; }; C4742B5F2984205000D5D950 /* ViewModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4742B5E2984205000D5D950 /* ViewModifiers.swift */; }; - C498E59F298D4DEA00E90DE0 /* LiveTimerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E59E298D4DEA00E90DE0 /* LiveTimerView.swift */; }; + C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */; }; C498E5A1298D543900E90DE0 /* LiveTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A0298D543900E90DE0 /* LiveTimer.swift */; }; + C498E5A3298D720600E90DE0 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C498E5A2298D720600E90DE0 /* TestView.swift */; }; C4F8B1532987FE6F005C86A5 /* LaunchWidgetLiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = C438C7D72981216200BF3EF9 /* LaunchWidgetLiveActivity.swift */; }; C4F8B15729891271005C86A5 /* Conductor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4F8B15629891271005C86A5 /* Conductor.swift */; }; C4F8B15929891528005C86A5 /* forest_stream.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C4F8B15829891528005C86A5 /* forest_stream.mp3 */; }; @@ -222,8 +223,9 @@ C4742B58298411E800D5D950 /* CountdownFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CountdownFormView.swift; sourceTree = ""; }; C4742B5A298414B000D5D950 /* ImageSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSelectionView.swift; sourceTree = ""; }; C4742B5E2984205000D5D950 /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = ""; }; - C498E59E298D4DEA00E90DE0 /* LiveTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerView.swift; sourceTree = ""; }; + C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = ""; }; C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = ""; }; + C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = ""; }; C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = ""; }; C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = ""; }; C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = ""; }; @@ -445,8 +447,9 @@ C4F8B1D3298BF686005C86A5 /* Components */, C4060DC1297AE73B003FAB80 /* ContentView.swift */, C4F8B1CF298BF2E2005C86A5 /* DialView.swift */, - C498E59E298D4DEA00E90DE0 /* LiveTimerView.swift */, + C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */, C438C80E29828B8600BF3EF9 /* RecordsView.swift */, + C498E5A2298D720600E90DE0 /* TestView.swift */, C4742B5A298414B000D5D950 /* ImageSelectionView.swift */, C4F8B165298A9ABB005C86A5 /* SoundImageFormView.swift */, C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */, @@ -748,6 +751,7 @@ C4742B59298411E800D5D950 /* CountdownFormView.swift in Sources */, C4060DC2297AE73B003FAB80 /* ContentView.swift in Sources */, C438C7C12980228B00BF3EF9 /* CountdownScheduler.swift in Sources */, + C498E5A3298D720600E90DE0 /* TestView.swift in Sources */, C445FA8F2987B83B0054D761 /* SoundPlayer.swift in Sources */, C4F8B1A7298AC2FC005C86A5 /* AbstractSoundTimer+CoreDataClass.swift in Sources */, C438C7C929803CA000BF3EF9 /* AppDelegate.swift in Sources */, @@ -771,7 +775,7 @@ C438C7C5298024E900BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, C4F8B15F298961A7005C86A5 /* ReorderableForEach.swift in Sources */, C4F8B1AC298AC3A0005C86A5 /* Alarm+CoreDataProperties.swift in Sources */, - C498E59F298D4DEA00E90DE0 /* LiveTimerView.swift in Sources */, + C498E59F298D4DEA00E90DE0 /* LiveTimerListView.swift in Sources */, C4060DC0297AE73B003FAB80 /* LeCountdownApp.swift in Sources */, C438C7E02981216300BF3EF9 /* LaunchWidget.intentdefinition in Sources */, C4742B5729840F6400D5D950 /* CoolPic.swift in Sources */, diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index d91be40..9981a58 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -25,15 +25,14 @@ extension AppDelegate: UNUserNotificationCenterDelegate { print("didReceive notification") -// Conductor.maestro.stopSoundIfNecessary() + Conductor.maestro.stopSoundIfPossible() } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { print("willPresent notification") completionHandler([.banner]) - Conductor.maestro.notifyUser(countdownId: notification.request.identifier, cancel: false) - + Conductor.maestro.notifyUser(countdownId: notification.request.identifier) } } diff --git a/LeCountdown/Conductor.swift b/LeCountdown/Conductor.swift index 19b04b2..41a8c20 100644 --- a/LeCountdown/Conductor.swift +++ b/LeCountdown/Conductor.swift @@ -9,6 +9,11 @@ import Foundation import ActivityKit import BackgroundTasks +enum Key : String { + case countdowns + case stopwatches +} + class Conductor : ObservableObject { static let maestro: Conductor = Conductor() @@ -43,16 +48,16 @@ class Conductor : ObservableObject { var countdowns = self.currentCountdowns.map { return LiveTimer(id: $0, date: $1.end) } - var stopwatches = self.currentStopwatches.map { + let stopwatches = self.currentStopwatches.map { return LiveTimer(id: $0, date: $1) } countdowns.append(contentsOf: stopwatches) self.liveTimers = countdowns.sorted() } - enum Key : String { - case countdowns - case stopwatches + func refreshHack(_ liveTimer: LiveTimer) { + + } func startCountdown(_ date: Date, countdown: Countdown) { @@ -64,12 +69,17 @@ class Conductor : ObservableObject { } } - func notifyUser(countdownId: String, cancel: Bool) { + func notifyUser(countdownId: String) { self._playSound(countdownId: countdownId) - endCountdown(countdownId: countdownId, cancel: cancel) + self._endCountdown(countdownId: countdownId, cancel: false) } - func endCountdown(countdownId: String, cancel: Bool) { + func cancelCountdown(id: String) { + CountdownScheduler.master.cancelCurrentNotifications(countdownId: id) + self._endCountdown(countdownId: id, cancel: true) + } + + fileprivate func _endCountdown(countdownId: String, cancel: Bool) { DispatchQueue.main.async { if !cancel { self._recordActivity(countdownId: countdownId) @@ -78,8 +88,6 @@ class Conductor : ObservableObject { if self.currentCountdowns.removeValue(forKey: countdownId) != nil { self._endLiveActivity(countdownId: countdownId) } - - self.stopSoundIfPossible() // multi use } } @@ -87,7 +95,7 @@ class Conductor : ObservableObject { let now = Date() for (key, value) in self.currentCountdowns { if value.end < now { - self.endCountdown(countdownId: key, cancel: false) + self._endCountdown(countdownId: key, cancel: false) } } } diff --git a/LeCountdown/CountdownScheduler.swift b/LeCountdown/CountdownScheduler.swift index cc9362d..9a7dec1 100644 --- a/LeCountdown/CountdownScheduler.swift +++ b/LeCountdown/CountdownScheduler.swift @@ -13,13 +13,13 @@ class CountdownScheduler { static let master = CountdownScheduler() func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result) -> Void) { - self.cancelCurrentNotifications(countdown: countdown) + self.cancelCurrentNotifications(countdownId: countdown.stringId) self._scheduleCountdownNotification(countdown: countdown, handler: handler) } - func cancelCurrentNotifications(countdown: Countdown) { - UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [countdown.stringId]) - Conductor.maestro.endCountdown(countdownId: countdown.stringId, cancel: true) + func cancelCurrentNotifications(countdownId: String) { + UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [countdownId]) +// Conductor.maestro.cancelCountdown(id: countdownId) } fileprivate func _scheduleCountdownNotification(countdown: Countdown, handler: @escaping (Result) -> Void) { diff --git a/LeCountdown/TimerRouter.swift b/LeCountdown/TimerRouter.swift index 59e6323..722b887 100644 --- a/LeCountdown/TimerRouter.swift +++ b/LeCountdown/TimerRouter.swift @@ -27,7 +27,7 @@ class TimerRouter { static func stopTimer(timer: AbstractTimer) { switch timer { case let countdown as Countdown: - Conductor.maestro.endCountdown(countdownId: countdown.stringId, cancel: true) + Conductor.maestro.cancelCountdown(id: countdown.stringId) case let stopwatch as Stopwatch: self._stopStopwatch(stopwatch) default: diff --git a/LeCountdown/Utils/TimeInterval+Extensions.swift b/LeCountdown/Utils/TimeInterval+Extensions.swift index 5b26c39..43823fe 100644 --- a/LeCountdown/Utils/TimeInterval+Extensions.swift +++ b/LeCountdown/Utils/TimeInterval+Extensions.swift @@ -9,6 +9,14 @@ import Foundation extension TimeInterval { + var hourMinuteSecondHS: String { + let h = self.hour + if h > 1 { + return String(format:"%d:%02d:%02d.%02d", hour, minute, second, hundredth) + } else { + return String(format:"%02d:%02d.%02d", minute, second, hundredth) + } + } var hourMinuteSecondMS: String { String(format:"%d:%02d:%02d.%03d", hour, minute, second, millisecond) } @@ -27,5 +35,8 @@ extension TimeInterval { var millisecond: Int { Int((self*1000).truncatingRemainder(dividingBy: 1000)) } + var hundredth: Int { + Int((self*100).truncatingRemainder(dividingBy: 100)) + } } diff --git a/LeCountdown/Views/ContentView.swift b/LeCountdown/Views/ContentView.swift index f2f6462..36e05d5 100644 --- a/LeCountdown/Views/ContentView.swift +++ b/LeCountdown/Views/ContentView.swift @@ -67,9 +67,12 @@ struct ContentView: View { } }.padding(.horizontal, itemSpacing) - LiveTimerView() .environment(\.managedObjectContext, viewContext) - .background(Color(white: 0.9)) - .cornerRadius(16.0, corners: [.topRight, .topLeft]) + if !conductor.liveTimers.isEmpty { + LiveTimerListView() .environment(\.managedObjectContext, viewContext) + .environmentObject(conductor) + .background(Color(white: 0.9)) + .cornerRadius(16.0, corners: [.topRight, .topLeft]) + } } } .navigationTitle("\(String(describing: T.self))") diff --git a/LeCountdown/Views/Countdown/CountdownDialView.swift b/LeCountdown/Views/Countdown/CountdownDialView.swift index af85c69..765a41a 100644 --- a/LeCountdown/Views/Countdown/CountdownDialView.swift +++ b/LeCountdown/Views/Countdown/CountdownDialView.swift @@ -18,24 +18,7 @@ struct CountdownDialView: View { HStack { VStack(alignment: .leading) { Text(countdown.activity?.name?.uppercased() ?? "") - // let dateInterval = DateInterval(start: Date(), end: Date()) - if let dateInterval = conductor.currentCountdowns[countdown.stringId] { - Text(dateInterval.end, style: .timer) - Spacer() - HStack { - Spacer() - Button { - CountdownScheduler.master.cancelCurrentNotifications(countdown: countdown) - } label: { - Text("Cancel".uppercased()) - } - .buttonStyle(.bordered) - Spacer() - } - - } else { - Text(countdown.duration.minuteSecond) - } + Text(countdown.duration.minuteSecond) Spacer() } Spacer() diff --git a/LeCountdown/Views/LiveTimerListView.swift b/LeCountdown/Views/LiveTimerListView.swift new file mode 100644 index 0000000..aa3263b --- /dev/null +++ b/LeCountdown/Views/LiveTimerListView.swift @@ -0,0 +1,123 @@ +// +// LiveTimerView.swift +// LeCountdown +// +// Created by Laurent Morvillier on 03/02/2023. +// + +import SwiftUI + +//class LiveTimerViewModel: ObservableObject { +// +// @Published var formattedTime: String = "" +// fileprivate var _formattingTimer: Timer? = nil +// +// var date: Date = Date() +// var showMilliseconds: Bool = false +// +// func startTimeUpdater(date: Date, showMilliseconds: Bool) { +// +// self.date = date +// self.showMilliseconds = showMilliseconds +// self._formattingTimer = Timer(timeInterval: 0.01, repeats: true, block: { timer in +// +// self.formattedTime = Date().timeIntervalSince(self.date).hourMinuteSecondMS +// +// }) +// self._formattingTimer?.fire() +// } +//} + +struct LiveTimerView: View { + + @Environment(\.managedObjectContext) private var viewContext + @EnvironmentObject var conductor: Conductor + + @State var timer: AbstractTimer + var date: Date + + var body: some View { + HStack { + Text(timer.displayName.uppercased()).padding() + + Spacer() + TimelineView(.periodic(from: self.date, by: 0.01)) { context in + Text(self._formattedDuration(date: context.date)) + .font(.title2) + .padding(.trailing) + .minimumScaleFactor(0.1) + } + + Button { + self._stopTimer(timer) + } label: { + Image(systemName: "xmark.circle.fill") + .font(.title) + .foregroundColor(.white) + .cornerRadius(8.0) + .frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity) + }.background(.red) + + } + } + + fileprivate func _formattedDuration(date: Date) -> String { + if self.timer is Stopwatch { + let duration = date.timeIntervalSince(self.date) + return duration.hourMinuteSecondHS + } else { // countdown + let duration = self.date.timeIntervalSince(date) + return duration.minuteSecond + } + } + + + fileprivate func _stopTimer(_ timer: AbstractTimer?) { + + guard let timer else { + return + } + + TimerRouter.stopTimer(timer: timer) + + } + +} + +struct LiveTimerListView: View { + + @Environment(\.managedObjectContext) private var viewContext + @EnvironmentObject var conductor: Conductor + + var body: some View { + LazyVStack { + ForEach(conductor.liveTimers) { liveTimer in + + if let timer: AbstractTimer = liveTimer.timer(context: self.viewContext) { + + LiveTimerView(timer: timer, date: liveTimer.date) + .frame(height: 55.0) + .foregroundColor(.white) + .monospaced() + .background(Color(white: 0.2)) + .cornerRadius(16.0) + } + + } + }.padding(8.0) + + } + + +} + +struct LiveTimerView_Previews: PreviewProvider { + + init() { + Conductor.maestro.currentCountdowns["fef"] = DateInterval(start: Date(), end: Date()) + } + + static var previews: some View { + LiveTimerListView().environmentObject(Conductor.maestro) + } +} diff --git a/LeCountdown/Views/LiveTimerView.swift b/LeCountdown/Views/LiveTimerView.swift deleted file mode 100644 index a011d68..0000000 --- a/LeCountdown/Views/LiveTimerView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// LiveTimerView.swift -// LeCountdown -// -// Created by Laurent Morvillier on 03/02/2023. -// - -import SwiftUI - -struct LiveTimerView: View { - - @Environment(\.managedObjectContext) private var viewContext - - @EnvironmentObject var conductor: Conductor - - var body: some View { - LazyVStack { - ForEach(conductor.liveTimers) { liveTimer in - - let timer = liveTimer.timer(context: self.viewContext) - - HStack { - Text(timer?.displayName.uppercased() ?? "missing") - Spacer() - Text(liveTimer.date, style: .timer) - Spacer() - Button { - self._stopTimer(timer) - } label: { - Text("STOP") - .padding(8.0) - .foregroundColor(.red) - .background(.white) - .fontWeight(.semibold) - .cornerRadius(8.0) - }//.buttonStyle(.bordered).tint(.red) - - } - .padding() - .frame(height: 55.0) - .foregroundColor(.white) - .monospaced() - .background(.cyan) - .cornerRadius(16.0) - } - }.padding(8.0) - } - - fileprivate func _stopTimer(_ timer: AbstractTimer?) { - - guard let timer else { - return - } - - TimerRouter.stopTimer(timer: timer) - - } - -} - -struct LiveTimerView_Previews: PreviewProvider { - - init() { - Conductor.maestro.currentCountdowns["fef"] = DateInterval(start: Date(), end: Date()) - } - - static var previews: some View { - LiveTimerView().environmentObject(Conductor.maestro) - } -} diff --git a/LeCountdown/Views/Stopwatch/StopwatchDialView.swift b/LeCountdown/Views/Stopwatch/StopwatchDialView.swift index 80d42df..eb6f52f 100644 --- a/LeCountdown/Views/Stopwatch/StopwatchDialView.swift +++ b/LeCountdown/Views/Stopwatch/StopwatchDialView.swift @@ -16,12 +16,7 @@ struct StopwatchDialView: View { var body: some View { HStack { VStack(alignment: .leading) { - Text(stopwatch.activity?.name?.uppercased() ?? "") - if let start = conductor.currentStopwatches[stopwatch.stringId] { - Text(start, style: .timer) - } - Spacer() } Spacer() diff --git a/LeCountdown/Views/TestView.swift b/LeCountdown/Views/TestView.swift new file mode 100644 index 0000000..65a7a41 --- /dev/null +++ b/LeCountdown/Views/TestView.swift @@ -0,0 +1,40 @@ +// +// TestView.swift +// LeCountdown +// +// Created by Laurent Morvillier on 03/02/2023. +// + +import SwiftUI + +struct TestSubView: View { + + var body: some View { + HStack { + Text("haha").padding() + Spacer() + Image(systemName: "xmark").frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity).background(.red) + } + .background(.gray) + .cornerRadius(16.0) + } + +} + +struct TestView: View { + var body: some View { + + LazyVStack { + TestSubView() + TestSubView() + TestSubView() + }.padding() + + } +} + +struct TestView_Previews: PreviewProvider { + static var previews: some View { + TestView() + } +}