Fixes timer refresh

release
Laurent 3 years ago
parent 7797796901
commit 035361f459
  1. 12
      LeCountdown.xcodeproj/project.pbxproj
  2. 5
      LeCountdown/AppDelegate.swift
  3. 28
      LeCountdown/Conductor.swift
  4. 8
      LeCountdown/CountdownScheduler.swift
  5. 2
      LeCountdown/TimerRouter.swift
  6. 11
      LeCountdown/Utils/TimeInterval+Extensions.swift
  7. 9
      LeCountdown/Views/ContentView.swift
  8. 19
      LeCountdown/Views/Countdown/CountdownDialView.swift
  9. 123
      LeCountdown/Views/LiveTimerListView.swift
  10. 70
      LeCountdown/Views/LiveTimerView.swift
  11. 5
      LeCountdown/Views/Stopwatch/StopwatchDialView.swift
  12. 40
      LeCountdown/Views/TestView.swift

@ -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 = "<group>"; };
C4742B5A298414B000D5D950 /* ImageSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSelectionView.swift; sourceTree = "<group>"; };
C4742B5E2984205000D5D950 /* ViewModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModifiers.swift; sourceTree = "<group>"; };
C498E59E298D4DEA00E90DE0 /* LiveTimerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerView.swift; sourceTree = "<group>"; };
C498E59E298D4DEA00E90DE0 /* LiveTimerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimerListView.swift; sourceTree = "<group>"; };
C498E5A0298D543900E90DE0 /* LiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveTimer.swift; sourceTree = "<group>"; };
C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = "<group>"; };
C4F8B15629891271005C86A5 /* Conductor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conductor.swift; sourceTree = "<group>"; };
C4F8B15829891528005C86A5 /* forest_stream.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = forest_stream.mp3; sourceTree = "<group>"; };
C4F8B15E298961A7005C86A5 /* ReorderableForEach.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReorderableForEach.swift; sourceTree = "<group>"; };
@ -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 */,

@ -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)
}
}

@ -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)
}
}
}

@ -13,13 +13,13 @@ class CountdownScheduler {
static let master = CountdownScheduler()
func scheduleIfPossible(countdown: Countdown, handler: @escaping (Result<Date?, Error>) -> 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<Date?, Error>) -> Void) {

@ -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:

@ -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))
}
}

@ -67,9 +67,12 @@ struct ContentView<T : AbstractTimer>: 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))")

@ -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()

@ -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)
}
}

@ -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)
}
}

@ -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()

@ -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()
}
}
Loading…
Cancel
Save