wip and fix

splits
Laurent 2 years ago
parent 2871a7abaa
commit 676fb0e97b
  1. 60
      LaunchWidget/LaunchWidgetLiveActivity.swift
  2. 14
      LeCountdown/Conductor.swift
  3. 39
      LeCountdown/CountdownSequence.swift
  4. 10
      LeCountdown/Views/DialView.swift
  5. 2
      LeCountdown/Views/TimersView.swift
  6. 3
      LeCountdown/Widget/LaunchWidgetAttributes.swift

@ -29,25 +29,34 @@ struct LiveActivityView: View {
struct LaunchWidgetLiveActivity: Widget { struct LaunchWidgetLiveActivity: Widget {
fileprivate let now: Date = Date()
var body: some WidgetConfiguration { var body: some WidgetConfiguration {
ActivityConfiguration(for: LaunchWidgetAttributes.self) { context in ActivityConfiguration(for: LaunchWidgetAttributes.self) { context in
// Lock screen/banner UI goes here // TimelineView(.periodic(from: self.now, by: 0.1)) { _ in
VStack(alignment: .leading) {
if context.attributes.isTimer { // Lock screen/banner UI goes here
let range = Date()...context.attributes.date VStack(alignment: .leading) {
Text(timerInterval: range,
pauseTime: range.lowerBound) let date: Date = self._date(context: context)
.font(.title) let name: String = self._name(context: context)
} else {
Text(context.attributes.date, style: .timer) if context.attributes.isCountdown {
let range = Date()...date
Text(timerInterval: range,
pauseTime: range.lowerBound)
.font(.title) .font(.title)
} else {
Text(context.attributes.date, style: .timer)
.font(.title)
}
Text(name.uppercased())
.font(.callout)
} }
Text(context.attributes.name.uppercased())
.font(.callout) // }
}
.padding() .padding()
.monospaced() .monospaced()
.background(Color(white: 0.1)) .background(Color(white: 0.1))
@ -60,10 +69,12 @@ struct LaunchWidgetLiveActivity: Widget {
DynamicIslandExpandedRegion(.leading) { DynamicIslandExpandedRegion(.leading) {
Text(context.attributes.name.uppercased()) Text(context.attributes.name.uppercased())
.monospaced() .monospaced()
.padding(.leading, 4.0)
} }
DynamicIslandExpandedRegion(.trailing) { DynamicIslandExpandedRegion(.trailing) {
Text(context.attributes.date, style: .timer) Text(context.attributes.date, style: .timer)
.monospaced() .monospaced()
.padding(.trailing, 4.0)
} }
DynamicIslandExpandedRegion(.bottom) { DynamicIslandExpandedRegion(.bottom) {
Button { Button {
@ -76,7 +87,7 @@ struct LaunchWidgetLiveActivity: Widget {
Text(context.attributes.name.uppercased()) Text(context.attributes.name.uppercased())
} compactTrailing: { } compactTrailing: {
Group { Group {
if context.attributes.isTimer { if context.attributes.isCountdown {
let range = Date()...context.attributes.date let range = Date()...context.attributes.date
Text(timerInterval: range, Text(timerInterval: range,
pauseTime: range.lowerBound) pauseTime: range.lowerBound)
@ -85,7 +96,7 @@ struct LaunchWidgetLiveActivity: Widget {
} }
}.multilineTextAlignment(.trailing) }.multilineTextAlignment(.trailing)
} minimal: { } minimal: {
if context.attributes.isTimer { if context.attributes.isCountdown {
let range = Date()...context.attributes.date let range = Date()...context.attributes.date
Text(timerInterval: range, Text(timerInterval: range,
pauseTime: range.lowerBound) pauseTime: range.lowerBound)
@ -100,9 +111,26 @@ struct LaunchWidgetLiveActivity: Widget {
} }
} }
fileprivate func _name(context: ActivityViewContext<LaunchWidgetAttributes>) -> String {
if let name = context.state.sequence?.currentStep.name {
return name
} else {
return context.attributes.name
}
}
fileprivate func _date(context: ActivityViewContext<LaunchWidgetAttributes>) -> Date {
if let date = context.state.sequence?.currentStep.interval.end {
return date
} else {
return context.attributes.date
}
}
fileprivate func _stop() { fileprivate func _stop() {
} }
} }
struct LaunchWidgetLiveActivity_Previews: PreviewProvider { struct LaunchWidgetLiveActivity_Previews: PreviewProvider {
@ -110,7 +138,7 @@ struct LaunchWidgetLiveActivity_Previews: PreviewProvider {
static let attributes = LaunchWidgetAttributes( static let attributes = LaunchWidgetAttributes(
id: "", id: "",
name: "Tea", name: "Tea",
date: Date().addingTimeInterval(3600.0), isTimer: true) date: Date().addingTimeInterval(3600.0), isCountdown: true)
static let contentState = LaunchWidgetAttributes.ContentState(ended: false) static let contentState = LaunchWidgetAttributes.ContentState(ended: false)

@ -93,8 +93,8 @@ class Conductor: ObservableObject {
fileprivate func _buildLiveTimers() { fileprivate func _buildLiveTimers() {
let liveCountdowns: [LiveTimer] = self.currentCountdowns.map { id, sequence in let liveCountdowns: [LiveTimer] = self.currentCountdowns.map { id, sequence in
let currentSpan = sequence.currentSpan let currentStep = sequence.currentStep
return LiveTimer(id: id, name: currentSpan.label, date: currentSpan.end) return LiveTimer(id: id, name: currentStep.label, date: currentStep.end)
} }
// add countdown if not present // add countdown if not present
for liveCountdown in liveCountdowns { for liveCountdown in liveCountdowns {
@ -217,6 +217,7 @@ class Conductor: ObservableObject {
// let countdownId = countdown.stringId // let countdownId = countdown.stringId
FileLogger.log("schedule countdown \(step.name ?? "''") at \(end)") FileLogger.log("schedule countdown \(step.name ?? "''") at \(end)")
Logger.log("schedule countdown \(step.name ?? "''") at \(end)")
let sound = step.someSound ?? countdown.someSound ?? Sound.default let sound = step.someSound ?? countdown.someSound ?? Sound.default
@ -300,7 +301,7 @@ class Conductor: ObservableObject {
func resumeCountdown(id: TimerID) throws { func resumeCountdown(id: TimerID) throws {
let now = Date() // let now = Date()
let context = PersistenceController.shared.container.viewContext let context = PersistenceController.shared.container.viewContext
if let countdown: Countdown = context.object(stringId: id), if let countdown: Countdown = context.object(stringId: id),
@ -325,7 +326,6 @@ class Conductor: ObservableObject {
// self.currentCountdowns[countdown.stringId] = CountdownSequence(steps: steps) // self.currentCountdowns[countdown.stringId] = CountdownSequence(steps: steps)
// TODO: RESUME
// _ = try self._scheduleSoundPlayer(countdown: countdown, in: remainingTime) // _ = try self._scheduleSoundPlayer(countdown: countdown, in: remainingTime)
} else { } else {
throw AppError.timerNotFound(id: id) throw AppError.timerNotFound(id: id)
@ -628,12 +628,14 @@ class Conductor: ObservableObject {
fileprivate func _launchLiveActivity(timer: AbstractTimer, date: Date) { fileprivate func _launchLiveActivity(timer: AbstractTimer, date: Date) {
guard let sequence = self.currentCountdowns[timer.stringId] else { return }
if #available(iOS 16.2, *) { if #available(iOS 16.2, *) {
if ActivityAuthorizationInfo().areActivitiesEnabled { if ActivityAuthorizationInfo().areActivitiesEnabled {
let contentState = LaunchWidgetAttributes.ContentState(ended: false) let contentState = LaunchWidgetAttributes.ContentState(ended: false, sequence: sequence)
let attributes = LaunchWidgetAttributes(id: timer.stringId, name: timer.displayName, date: date, isTimer: timer is Countdown) let attributes = LaunchWidgetAttributes(id: timer.stringId, name: timer.displayName, date: date, isCountdown: timer is Countdown)
let activityContent = ActivityContent(state: contentState, staleDate: nil) let activityContent = ActivityContent(state: contentState, staleDate: nil)
do { do {

@ -8,7 +8,8 @@
import Foundation import Foundation
import CoreData import CoreData
class CountdownSequence: Codable { class CountdownSequence: Codable, Equatable, Hashable {
var steps: [CountdownStep] var steps: [CountdownStep]
var pauseDate: Date? = nil var pauseDate: Date? = nil
@ -17,7 +18,7 @@ class CountdownSequence: Codable {
self.pauseDate = pauseDate self.pauseDate = pauseDate
} }
var currentSpan: CountdownStep { var currentStep: CountdownStep {
let referenceDate = self.pauseDate ?? Date() let referenceDate = self.pauseDate ?? Date()
let current: CountdownStep? = self.steps.first { span in let current: CountdownStep? = self.steps.first { span in
return span.interval.start < referenceDate && span.interval.end > referenceDate return span.interval.start < referenceDate && span.interval.end > referenceDate
@ -26,7 +27,7 @@ class CountdownSequence: Codable {
} }
var currentEnd: Date { var currentEnd: Date {
return self.currentSpan.interval.end return self.currentStep.interval.end
} }
var dateInterval: DateInterval { var dateInterval: DateInterval {
@ -67,11 +68,23 @@ class CountdownSequence: Codable {
self.pauseDate = nil self.pauseDate = nil
} }
// MARK: - Equatable / Hashable
static func == (lhs: CountdownSequence, rhs: CountdownSequence) -> Bool {
return lhs.steps == rhs.steps && lhs.pauseDate == rhs.pauseDate
}
func hash(into hasher: inout Hasher) {
hasher.combine(steps)
hasher.combine(pauseDate)
}
private static let defaultSpan = CountdownStep(interval: DateInterval(start: Date(), end: Date()), name: "none", index: 0, loopCount: 1, stepId: "") private static let defaultSpan = CountdownStep(interval: DateInterval(start: Date(), end: Date()), name: "none", index: 0, loopCount: 1, stepId: "")
} }
class CountdownStep: Codable { class CountdownStep: Codable, Equatable, Hashable {
var interval: DateInterval var interval: DateInterval
var name: String? var name: String?
var index: Int16 var index: Int16
@ -115,4 +128,22 @@ class CountdownStep: Codable {
self.interval = DateInterval(start: start, end: end) self.interval = DateInterval(start: start, end: end)
} }
// MARK: - Equatable / Hashable
static func == (lhs: CountdownStep, rhs: CountdownStep) -> Bool {
return lhs.interval == rhs.interval
&& lhs.name == rhs.name
&& lhs.index == rhs.index
&& lhs.loopCount == rhs.loopCount
&& lhs.stepId == rhs.stepId
}
func hash(into hasher: inout Hasher) {
hasher.combine(interval)
hasher.combine(name)
hasher.combine(index)
hasher.combine(loopCount)
hasher.combine(stepId)
}
} }

@ -67,10 +67,10 @@ struct DialView: View {
} }
.background(self._dialBackgroundColor) .background(self._dialBackgroundColor)
.frame(width: frameSize, height: self._height) .frame(width: self.frameSize, height: self._height)
.cornerRadius(20.0) .cornerRadius(20.0)
} }
fileprivate var _height: CGFloat { fileprivate var _height: CGFloat {
return UIDevice.isPhoneIdiom ? 80.0 : 200.0 return UIDevice.isPhoneIdiom ? 80.0 : 200.0
} }
@ -94,13 +94,15 @@ struct DialView: View {
Group { Group {
switch self.timer { switch self.timer {
case let countdown as Countdown: case let countdown as Countdown:
CountdownDialView(countdown: countdown, isEditing: self.isEditingBinding.wrappedValue) CountdownDialView(countdown: countdown,
isEditing: self.isEditingBinding.wrappedValue)
.environmentObject(Conductor.maestro) .environmentObject(Conductor.maestro)
case let alarm as Alarm: case let alarm as Alarm:
AlarmDialView(alarm: alarm) AlarmDialView(alarm: alarm)
.environmentObject(Conductor.maestro) .environmentObject(Conductor.maestro)
case let stopwatch as Stopwatch: case let stopwatch as Stopwatch:
StopwatchDialView(stopwatch: stopwatch, isEditing: self.isEditingBinding.wrappedValue) StopwatchDialView(stopwatch: stopwatch,
isEditing: self.isEditingBinding.wrappedValue)
.environmentObject(Conductor.maestro) .environmentObject(Conductor.maestro)
default: default:
Text("missing dial view") Text("missing dial view")

@ -34,7 +34,7 @@ struct TimersView: View {
GeometryReader { reader in GeometryReader { reader in
let columns: [GridItem] = self._columns() let columns: [GridItem] = self._columns()
let width: CGFloat = reader.size.width / CGFloat(columns.count) - 5.0 let width: CGFloat = max(reader.size.width / CGFloat(columns.count) - 5.0, 0.0)
ScrollView { ScrollView {

@ -12,12 +12,13 @@ struct LaunchWidgetAttributes: ActivityAttributes {
public struct ContentState: Codable, Hashable { public struct ContentState: Codable, Hashable {
// Dynamic stateful properties about your activity go here! // Dynamic stateful properties about your activity go here!
var ended: Bool var ended: Bool
var sequence: CountdownSequence? = nil
} }
// Fixed non-changing properties about your activity go here! // Fixed non-changing properties about your activity go here!
var id: String var id: String
var name: String var name: String
var date: Date var date: Date
var isTimer: Bool var isCountdown: Bool
} }

Loading…
Cancel
Save