|
|
|
|
@ -7,56 +7,160 @@ |
|
|
|
|
|
|
|
|
|
import SwiftUI |
|
|
|
|
|
|
|
|
|
struct LiveTimerView: View { |
|
|
|
|
struct GreenCheckmarkView: View { |
|
|
|
|
var body: some View { |
|
|
|
|
Image(systemName: "checkmark.circle.fill") |
|
|
|
|
.foregroundColor(.green) |
|
|
|
|
.font(.title) |
|
|
|
|
.frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class LiveStopwatchModel: ObservableObject { |
|
|
|
|
|
|
|
|
|
@Published var endDate: Date? = nil |
|
|
|
|
|
|
|
|
|
func stop(_ stopwatch: Stopwatch) { |
|
|
|
|
|
|
|
|
|
let now = Date() |
|
|
|
|
self.endDate = now |
|
|
|
|
|
|
|
|
|
TimerRouter.stopStopwatch(stopwatch, end: now) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct LiveStopwatchView: View { |
|
|
|
|
|
|
|
|
|
@Environment(\.managedObjectContext) private var viewContext |
|
|
|
|
@EnvironmentObject var conductor: Conductor |
|
|
|
|
|
|
|
|
|
@State var timer: AbstractTimer |
|
|
|
|
@StateObject var model: LiveStopwatchModel = LiveStopwatchModel() |
|
|
|
|
|
|
|
|
|
@State var stopwatch: Stopwatch |
|
|
|
|
var date: Date |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
|
|
let running = (self.model.endDate == nil) |
|
|
|
|
|
|
|
|
|
HStack { |
|
|
|
|
Text(timer.displayName.uppercased()).padding() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Text(stopwatch.displayName.uppercased()).padding() |
|
|
|
|
|
|
|
|
|
Spacer() |
|
|
|
|
TimelineView(.periodic(from: self.date, by: 0.01)) { context in |
|
|
|
|
Text(self._formattedDuration(date: context.date)) |
|
|
|
|
|
|
|
|
|
if running { |
|
|
|
|
TimelineView(.periodic(from: self.date, by: 0.01)) { context in |
|
|
|
|
Text(self._formattedDuration(date: context.date)) |
|
|
|
|
.font(.title2) |
|
|
|
|
.padding(.trailing) |
|
|
|
|
.minimumScaleFactor(0.1) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
let duration = self.model.endDate?.timeIntervalSince(self.date) ?? 0.0 |
|
|
|
|
Text(duration.hourMinuteSecondHS) |
|
|
|
|
.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) |
|
|
|
|
if running { |
|
|
|
|
|
|
|
|
|
Button { |
|
|
|
|
self.model.stop(stopwatch) |
|
|
|
|
} label: { |
|
|
|
|
|
|
|
|
|
Image(systemName: "stop.circle.fill") |
|
|
|
|
.font(.title) |
|
|
|
|
.foregroundColor(.white) |
|
|
|
|
.cornerRadius(8.0) |
|
|
|
|
.frame(minWidth: 0.0, maxWidth: 60.0, minHeight: 0.0, maxHeight: .infinity) |
|
|
|
|
}.background(.red) |
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
GreenCheckmarkView() |
|
|
|
|
} |
|
|
|
|
}.onTapGesture { |
|
|
|
|
withAnimation { |
|
|
|
|
self._dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.frame(height: 55.0) |
|
|
|
|
.foregroundColor(.white) |
|
|
|
|
.monospaced() |
|
|
|
|
.background(Color(white: 0.2)) |
|
|
|
|
.cornerRadius(16.0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _dismiss() { |
|
|
|
|
conductor.removeLiveTimer(id: self.stopwatch.stringId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
let duration = date.timeIntervalSince(self.date) |
|
|
|
|
return duration.hourMinuteSecondHS |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct LiveCountdownView: View { |
|
|
|
|
|
|
|
|
|
fileprivate func _stopTimer(_ timer: AbstractTimer?) { |
|
|
|
|
|
|
|
|
|
guard let timer else { |
|
|
|
|
return |
|
|
|
|
@Environment(\.managedObjectContext) private var viewContext |
|
|
|
|
@EnvironmentObject var conductor: Conductor |
|
|
|
|
|
|
|
|
|
@State var countdown: Countdown |
|
|
|
|
var date: Date |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
HStack { |
|
|
|
|
Text(self.countdown.displayName.uppercased()).padding() |
|
|
|
|
Spacer() |
|
|
|
|
TimelineView(.periodic(from: self.date, by: 0.01)) { context in |
|
|
|
|
|
|
|
|
|
if self.date > context.date { |
|
|
|
|
|
|
|
|
|
HStack { |
|
|
|
|
Text(self._formattedDuration(date: context.date)) |
|
|
|
|
.font(.title2) |
|
|
|
|
.minimumScaleFactor(0.1) |
|
|
|
|
.padding(.trailing) |
|
|
|
|
Button { |
|
|
|
|
self._cancelCountdown() |
|
|
|
|
} 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) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
GreenCheckmarkView() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}.onTapGesture { |
|
|
|
|
withAnimation { |
|
|
|
|
self._dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
TimerRouter.stopTimer(timer: timer) |
|
|
|
|
|
|
|
|
|
.frame(height: 55.0) |
|
|
|
|
.foregroundColor(.white) |
|
|
|
|
.monospaced() |
|
|
|
|
.background(Color(white: 0.2)) |
|
|
|
|
.cornerRadius(16.0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _dismiss() { |
|
|
|
|
conductor.removeLiveTimer(id: self.countdown.stringId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _formattedDuration(date: Date) -> String { |
|
|
|
|
let duration = self.date.timeIntervalSince(date) |
|
|
|
|
return duration.minuteSecond |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _cancelCountdown() { |
|
|
|
|
Conductor.maestro.cancelCountdown(id: self.countdown.stringId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
@ -72,12 +176,15 @@ struct LiveTimerListView: View { |
|
|
|
|
|
|
|
|
|
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) |
|
|
|
|
switch timer { |
|
|
|
|
case let cd as Countdown: |
|
|
|
|
LiveCountdownView(countdown: cd, date: liveTimer.date) |
|
|
|
|
case let sw as Stopwatch: |
|
|
|
|
LiveStopwatchView(stopwatch: sw, date: liveTimer.date) |
|
|
|
|
default: |
|
|
|
|
Text("unmanaged timer: \(timer)") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
@ -90,11 +197,17 @@ struct LiveTimerListView: View { |
|
|
|
|
|
|
|
|
|
struct LiveTimerView_Previews: PreviewProvider { |
|
|
|
|
|
|
|
|
|
init() { |
|
|
|
|
Conductor.maestro.currentCountdowns["fef"] = DateInterval(start: Date(), end: Date()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static var previews: some View { |
|
|
|
|
LiveTimerListView().environmentObject(Conductor.maestro) |
|
|
|
|
// LiveTimerListView().environmentObject(Conductor.maestro) |
|
|
|
|
|
|
|
|
|
Group { |
|
|
|
|
VStack(spacing: 20.0) { |
|
|
|
|
LiveCountdownView(countdown: Countdown.fake(context: PersistenceController.preview.container.viewContext), date: Date().addingTimeInterval(3600.0)) |
|
|
|
|
.environmentObject(Conductor.maestro) |
|
|
|
|
LiveStopwatchView(stopwatch: Stopwatch.fake(context: PersistenceController.preview.container.viewContext), date: Date()) |
|
|
|
|
.environmentObject(Conductor.maestro) |
|
|
|
|
} |
|
|
|
|
}.padding() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|