|
|
|
|
@ -21,19 +21,9 @@ class LiveStopwatchModel: ObservableObject { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
struct SeparatorView: View { |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
Spacer() |
|
|
|
|
.frame(minWidth: 0.0, maxWidth: .infinity, minHeight: 1.0, maxHeight: 1.0) |
|
|
|
|
.background(Color(white: 0.85)) |
|
|
|
|
.padding(.vertical, 8.0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate let liveViewSize: CGFloat = 70.0 |
|
|
|
|
fileprivate let timerFontSize: CGFloat = 32.0 |
|
|
|
|
fileprivate let actionButtonFontSize: CGFloat = 36.0 |
|
|
|
|
|
|
|
|
|
struct TimeView: View { |
|
|
|
|
|
|
|
|
|
@ -61,58 +51,51 @@ struct LiveStopwatchView: View { |
|
|
|
|
|
|
|
|
|
let running = (self.model.endDate == nil) |
|
|
|
|
|
|
|
|
|
VStack(alignment: .trailing) { |
|
|
|
|
HStack { |
|
|
|
|
|
|
|
|
|
if running { |
|
|
|
|
TimelineView(.periodic(from: self.date, by: 0.01)) { context in |
|
|
|
|
|
|
|
|
|
TimeView(text: self._formattedDuration(date: context.date)) |
|
|
|
|
VStack(alignment: .leading) { |
|
|
|
|
|
|
|
|
|
if running { |
|
|
|
|
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 |
|
|
|
|
TimeView(text: duration.hourMinuteSecondHS) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
let duration = self.model.endDate?.timeIntervalSince(self.date) ?? 0.0 |
|
|
|
|
TimeView(text: duration.hourMinuteSecondHS) |
|
|
|
|
Text(stopwatch.displayName.uppercased()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Spacer() |
|
|
|
|
|
|
|
|
|
// 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() |
|
|
|
|
// } |
|
|
|
|
// SeparatorView() |
|
|
|
|
HStack { |
|
|
|
|
Group { |
|
|
|
|
if !running { |
|
|
|
|
GreenCheckmarkView() |
|
|
|
|
} |
|
|
|
|
Text(stopwatch.displayName.uppercased()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}.onTapGesture { |
|
|
|
|
withAnimation { |
|
|
|
|
if self.model.endDate == nil { |
|
|
|
|
self.model.stop(stopwatch) |
|
|
|
|
} else { |
|
|
|
|
self._dismiss() |
|
|
|
|
Image(systemName: "stop.circle.fill") |
|
|
|
|
.foregroundColor(.accentColor) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}.font(.system(size: actionButtonFontSize)) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
.frame(height: liveViewSize) |
|
|
|
|
// .foregroundColor(.white) |
|
|
|
|
.monospaced() |
|
|
|
|
// .background(Color(white: 0.2)) |
|
|
|
|
// .cornerRadius(16.0) |
|
|
|
|
.frame(height: liveViewSize) |
|
|
|
|
.contentShape(Rectangle()) |
|
|
|
|
.onTapGesture { |
|
|
|
|
self._actionHandler() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _actionHandler() { |
|
|
|
|
withAnimation { |
|
|
|
|
if self.model.endDate == nil { |
|
|
|
|
self.model.stop(stopwatch) |
|
|
|
|
} else { |
|
|
|
|
self._dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _dismiss() { |
|
|
|
|
conductor.removeLiveTimer(id: self.stopwatch.stringId) |
|
|
|
|
} |
|
|
|
|
@ -134,50 +117,58 @@ struct LiveCountdownView: View { |
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
|
|
TimelineView(.periodic(from: self.date, by: 0.01)) { context in |
|
|
|
|
VStack(alignment: .trailing) { |
|
|
|
|
|
|
|
|
|
HStack { |
|
|
|
|
|
|
|
|
|
let running = self.date > context.date |
|
|
|
|
let cancelled = conductor.cancelledCountdowns.contains(where: { $0 == self.countdown.stringId }) |
|
|
|
|
|
|
|
|
|
if cancelled { |
|
|
|
|
TimeView(text: NSLocalizedString("Cancelled", comment: "")) |
|
|
|
|
} else if running { |
|
|
|
|
TimeView(text: self._formattedDuration(date: context.date)) |
|
|
|
|
} else { |
|
|
|
|
TimeView(text: self.date.formatted(date: .omitted, time: .shortened)) |
|
|
|
|
|
|
|
|
|
VStack(alignment: .leading) { |
|
|
|
|
|
|
|
|
|
if cancelled { |
|
|
|
|
TimeView(text: NSLocalizedString("Cancelled", comment: "")) |
|
|
|
|
} else if running { |
|
|
|
|
TimeView(text: self._formattedDuration(date: context.date)) |
|
|
|
|
} else { |
|
|
|
|
TimeView(text: self.date.formatted(date: .omitted, time: .shortened)) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Text(self.countdown.displayName.uppercased()) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
// SeparatorView() |
|
|
|
|
HStack { |
|
|
|
|
Spacer() |
|
|
|
|
|
|
|
|
|
Group { |
|
|
|
|
if cancelled { |
|
|
|
|
XMarkView() |
|
|
|
|
Image(systemName: "xmark.circle").foregroundColor(.accentColor) |
|
|
|
|
} else if !running { |
|
|
|
|
GreenCheckmarkView() |
|
|
|
|
} else { |
|
|
|
|
Image(systemName: "xmark.circle.fill").foregroundColor(.accentColor) |
|
|
|
|
} |
|
|
|
|
Text(self.countdown.displayName.uppercased()) |
|
|
|
|
// .padding(.top, 2.0) |
|
|
|
|
} |
|
|
|
|
}.font(.system(size: actionButtonFontSize)) |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
.contentShape(Rectangle()) // make the onTap react everywhere |
|
|
|
|
.contentShape(Rectangle()) |
|
|
|
|
.onTapGesture { |
|
|
|
|
withAnimation { |
|
|
|
|
if conductor.currentCountdowns[self.countdown.stringId] != nil { |
|
|
|
|
self._cancelCountdown() |
|
|
|
|
} else { |
|
|
|
|
self._dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
self._actionHandler() |
|
|
|
|
} |
|
|
|
|
.frame(height: liveViewSize) |
|
|
|
|
// .foregroundColor(.white) |
|
|
|
|
.monospaced() |
|
|
|
|
// .background(Color(white: 0.2)) |
|
|
|
|
// .cornerRadius(16.0) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _actionHandler() { |
|
|
|
|
withAnimation { |
|
|
|
|
if conductor.currentCountdowns[self.countdown.stringId] != nil { |
|
|
|
|
self._cancelCountdown() |
|
|
|
|
} else { |
|
|
|
|
self._dismiss() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fileprivate func _dismiss() { |
|
|
|
|
// conductor.cancelCountdown(id: self.countdown.stringId) |
|
|
|
|
conductor.removeLiveTimer(id: self.countdown.stringId) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -199,31 +190,27 @@ struct LiveTimerListView: View { |
|
|
|
|
|
|
|
|
|
var body: some View { |
|
|
|
|
|
|
|
|
|
// ScrollView { |
|
|
|
|
// ScrollView { |
|
|
|
|
|
|
|
|
|
VStack { |
|
|
|
|
|
|
|
|
|
LazyVGrid( |
|
|
|
|
columns: self._columns(), |
|
|
|
|
alignment: .trailing, |
|
|
|
|
spacing: 0.0 |
|
|
|
|
) { |
|
|
|
|
ForEach(conductor.liveTimers) { liveTimer in |
|
|
|
|
|
|
|
|
|
ForEach(conductor.liveTimers) { liveTimer in |
|
|
|
|
if let timer: AbstractTimer = liveTimer.timer(context: self.viewContext) { |
|
|
|
|
|
|
|
|
|
if let timer: AbstractTimer = liveTimer.timer(context: self.viewContext) { |
|
|
|
|
|
|
|
|
|
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)") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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)") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}.padding() |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
@ -231,7 +218,7 @@ struct LiveTimerListView: View { |
|
|
|
|
fileprivate func _columnCount() -> Int { |
|
|
|
|
#if os(iOS) |
|
|
|
|
if UIDevice.isPhoneIdiom { |
|
|
|
|
return 2 |
|
|
|
|
return 18 |
|
|
|
|
} else { |
|
|
|
|
return 3 |
|
|
|
|
} |
|
|
|
|
|