Split IntervalTimer model into view and coredata models
Trying to use the same object for the view and Core Data was a bit clumsy and made working with SwiftUI previews difficult. Splitting them up fixed the issue at the expense of a little more maintenance work.
This commit is contained in:
parent
cedd5403c5
commit
ead9b2ebe0
7 changed files with 88 additions and 31 deletions
|
@ -127,7 +127,25 @@ struct LabeledCounter: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ActiveTimerView_Previews: PreviewProvider {
|
struct ActiveTimerView_Previews: PreviewProvider {
|
||||||
|
static var dataStore: TimerDataStore {
|
||||||
|
get {
|
||||||
|
let store = TimerDataStore() {}
|
||||||
|
store.openTimer(IntervalTimer(
|
||||||
|
id: UUID(),
|
||||||
|
name: "Test",
|
||||||
|
description: nil,
|
||||||
|
warmUpDuration: 300,
|
||||||
|
lowIntensityDuration: 10,
|
||||||
|
highIntensityDuration: 20,
|
||||||
|
restDuration: 50,
|
||||||
|
cooldownDuration: 300,
|
||||||
|
sets: 4,
|
||||||
|
rounds: 2
|
||||||
|
))
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
}
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
ActiveTimerView().environmentObject(TimerDataStore() {})
|
ActiveTimerView().environmentObject(dataStore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,20 @@ import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class IntervalTimer: NSManagedObject, Identifiable {
|
struct IntervalTimer: Identifiable, Equatable {
|
||||||
|
let id: UUID?
|
||||||
|
let name: String
|
||||||
|
let description: String?
|
||||||
|
let warmUpDuration: Int64
|
||||||
|
let lowIntensityDuration: Int64
|
||||||
|
let highIntensityDuration: Int64
|
||||||
|
let restDuration: Int64
|
||||||
|
let cooldownDuration: Int64
|
||||||
|
let sets: Int64
|
||||||
|
let rounds: Int64
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntervalTimerMO: NSManagedObject, Identifiable {
|
||||||
@NSManaged var id: UUID?
|
@NSManaged var id: UUID?
|
||||||
@NSManaged var name: String
|
@NSManaged var name: String
|
||||||
@NSManaged var userDescription: String?
|
@NSManaged var userDescription: String?
|
||||||
|
@ -72,6 +85,34 @@ extension IntervalTimer {
|
||||||
return warmUpDuration + ((((lowIntensityDuration + highIntensityDuration) * sets) + restDuration) * rounds) + cooldownDuration
|
return warmUpDuration + ((((lowIntensityDuration + highIntensityDuration) * sets) + restDuration) * rounds) + cooldownDuration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copy(toMO: IntervalTimerMO) {
|
||||||
|
toMO.id = self.id
|
||||||
|
toMO.name = self.name
|
||||||
|
toMO.userDescription = self.description
|
||||||
|
toMO.warmUpDuration = self.warmUpDuration
|
||||||
|
toMO.lowIntensityDuration = self.lowIntensityDuration
|
||||||
|
toMO.highIntensityDuration = self.highIntensityDuration
|
||||||
|
toMO.restDuration = self.restDuration
|
||||||
|
toMO.cooldownDuration = self.cooldownDuration
|
||||||
|
toMO.sets = self.sets
|
||||||
|
toMO.rounds = self.rounds
|
||||||
|
}
|
||||||
|
|
||||||
|
static func create(fromMO: IntervalTimerMO) -> IntervalTimer {
|
||||||
|
return IntervalTimer(
|
||||||
|
id: fromMO.id,
|
||||||
|
name: fromMO.name,
|
||||||
|
description: fromMO.userDescription,
|
||||||
|
warmUpDuration: fromMO.warmUpDuration,
|
||||||
|
lowIntensityDuration: fromMO.lowIntensityDuration,
|
||||||
|
highIntensityDuration: fromMO.highIntensityDuration,
|
||||||
|
restDuration: fromMO.restDuration,
|
||||||
|
cooldownDuration: fromMO.cooldownDuration,
|
||||||
|
sets: fromMO.sets,
|
||||||
|
rounds: fromMO.rounds
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ActiveTimerState {
|
struct ActiveTimerState {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H2" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="17192" systemVersion="19H2" minimumToolsVersion="Automatic" sourceLanguage="Swift" usedWithCloudKit="YES" userDefinedModelVersionIdentifier="">
|
||||||
<entity name="IntervalTimer" representedClassName=".IntervalTimer" syncable="YES">
|
<entity name="IntervalTimer" representedClassName=".IntervalTimerMO" syncable="YES">
|
||||||
<attribute name="cooldownDuration" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="cooldownDuration" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="highIntensityDuration" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
<attribute name="highIntensityDuration" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||||
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||||
|
|
|
@ -183,20 +183,14 @@ class TimerDataStore: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTimers() {
|
func loadTimers() {
|
||||||
DispatchQueue.global(qos: .background).async {
|
|
||||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
||||||
do {
|
do {
|
||||||
let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer]
|
let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO]
|
||||||
DispatchQueue.main.async {
|
self.timers = .success(fetchedTimers.map { IntervalTimer.create(fromMO: $0) })
|
||||||
self.timers = .success(fetchedTimers)
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.timers = .failure(.failed(error))
|
self.timers = .failure(.failed(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveTimer(
|
func saveTimer(
|
||||||
id: UUID? = nil,
|
id: UUID? = nil,
|
||||||
|
@ -210,18 +204,11 @@ class TimerDataStore: ObservableObject {
|
||||||
sets: Int64,
|
sets: Int64,
|
||||||
rounds: Int64
|
rounds: Int64
|
||||||
) {
|
) {
|
||||||
var timer: IntervalTimer
|
var timer: IntervalTimerMO
|
||||||
if let uuid = id {
|
if let uuid = id {
|
||||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
timer = loadTimer(byId: uuid)!
|
||||||
fetchRequest.predicate = NSPredicate(format: "id = %@", uuid.uuidString)
|
|
||||||
let result = try! persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer]
|
|
||||||
if result.count == 1 {
|
|
||||||
timer = result[0]
|
|
||||||
} else {
|
} else {
|
||||||
timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer
|
timer = IntervalTimerMO.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimerMO
|
||||||
}
|
|
||||||
} else {
|
|
||||||
timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
timer.id = id ?? UUID()
|
timer.id = id ?? UUID()
|
||||||
|
@ -242,19 +229,31 @@ class TimerDataStore: ObservableObject {
|
||||||
loadTimers()
|
loadTimers()
|
||||||
if let currentState = activeTimer {
|
if let currentState = activeTimer {
|
||||||
if currentState.timer.id == timer.id {
|
if currentState.timer.id == timer.id {
|
||||||
activeTimer = currentState.copy(timer: timer)
|
activeTimer = currentState.copy(timer: IntervalTimer.create(fromMO: timer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteTimer(at: IndexSet) {
|
func deleteTimer(at: IndexSet) {
|
||||||
let timer = try! self.timers.get()[at.first!]
|
guard let timer = loadTimer(byId: try! self.timers.get()[at.first!].id!) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let viewContext = persistentContainer.viewContext
|
let viewContext = persistentContainer.viewContext
|
||||||
viewContext.delete(timer)
|
viewContext.delete(timer)
|
||||||
try! viewContext.save()
|
try! viewContext.save()
|
||||||
loadTimers()
|
loadTimers()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func loadTimer(byId: UUID) -> IntervalTimerMO? {
|
||||||
|
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
||||||
|
fetchRequest.predicate = NSPredicate(format: "id = %@", byId.uuidString)
|
||||||
|
let result = try! persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO]
|
||||||
|
if result.count == 1 {
|
||||||
|
return result[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
private func loadSound(_ phase: Phase) {
|
private func loadSound(_ phase: Phase) {
|
||||||
let filePath = Bundle.main.path(forResource: phase.rawValue, ofType: "mp3")
|
let filePath = Bundle.main.path(forResource: phase.rawValue, ofType: "mp3")
|
||||||
let url = NSURL(fileURLWithPath: filePath!)
|
let url = NSURL(fileURLWithPath: filePath!)
|
||||||
|
|
|
@ -162,7 +162,7 @@ struct TimerFormView: View {
|
||||||
self.title = "New Timer"
|
self.title = "New Timer"
|
||||||
}
|
}
|
||||||
self._name = State(initialValue: timer?.name ?? "")
|
self._name = State(initialValue: timer?.name ?? "")
|
||||||
self._description = State(initialValue: timer?.userDescription ?? "")
|
self._description = State(initialValue: timer?.description ?? "")
|
||||||
self._warmDuration = State(initialValue: timer?.warmUpDuration ?? 300)
|
self._warmDuration = State(initialValue: timer?.warmUpDuration ?? 300)
|
||||||
self._lowDuration = State(initialValue: timer?.lowIntensityDuration ?? 30)
|
self._lowDuration = State(initialValue: timer?.lowIntensityDuration ?? 30)
|
||||||
self._highDuration = State(initialValue: timer?.highIntensityDuration ?? 60)
|
self._highDuration = State(initialValue: timer?.highIntensityDuration ?? 60)
|
||||||
|
|
|
@ -29,8 +29,8 @@ struct TimerListView: View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text(timer.name)
|
Text(timer.name)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
if timer.userDescription?.count ?? 0 > 0 {
|
if timer.description?.count ?? 0 > 0 {
|
||||||
Text(timer.userDescription!)
|
Text(timer.description!)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
|
@ -30,5 +30,4 @@ class IntervalTimerTests: XCTestCase {
|
||||||
// Put the code you want to measure the time of here.
|
// Put the code you want to measure the time of here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue