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 {
|
||||
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 {
|
||||
ActiveTimerView().environmentObject(TimerDataStore() {})
|
||||
ActiveTimerView().environmentObject(dataStore)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,20 @@ import CoreData
|
|||
import Foundation
|
||||
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 name: String
|
||||
@NSManaged var userDescription: String?
|
||||
|
@ -72,6 +85,34 @@ extension IntervalTimer {
|
|||
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 {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?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="">
|
||||
<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="highIntensityDuration" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
|
|
|
@ -183,18 +183,12 @@ class TimerDataStore: ObservableObject {
|
|||
}
|
||||
|
||||
func loadTimers() {
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
||||
do {
|
||||
let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer]
|
||||
DispatchQueue.main.async {
|
||||
self.timers = .success(fetchedTimers)
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
self.timers = .failure(.failed(error))
|
||||
}
|
||||
}
|
||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
||||
do {
|
||||
let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO]
|
||||
self.timers = .success(fetchedTimers.map { IntervalTimer.create(fromMO: $0) })
|
||||
} catch {
|
||||
self.timers = .failure(.failed(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -210,18 +204,11 @@ class TimerDataStore: ObservableObject {
|
|||
sets: Int64,
|
||||
rounds: Int64
|
||||
) {
|
||||
var timer: IntervalTimer
|
||||
var timer: IntervalTimerMO
|
||||
if let uuid = id {
|
||||
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "IntervalTimer")
|
||||
fetchRequest.predicate = NSPredicate(format: "id = %@", uuid.uuidString)
|
||||
let result = try! persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimer]
|
||||
if result.count == 1 {
|
||||
timer = result[0]
|
||||
} else {
|
||||
timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer
|
||||
}
|
||||
timer = loadTimer(byId: uuid)!
|
||||
} 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
|
||||
}
|
||||
|
||||
timer.id = id ?? UUID()
|
||||
|
@ -242,19 +229,31 @@ class TimerDataStore: ObservableObject {
|
|||
loadTimers()
|
||||
if let currentState = activeTimer {
|
||||
if currentState.timer.id == timer.id {
|
||||
activeTimer = currentState.copy(timer: timer)
|
||||
activeTimer = currentState.copy(timer: IntervalTimer.create(fromMO: timer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
viewContext.delete(timer)
|
||||
try! viewContext.save()
|
||||
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) {
|
||||
let filePath = Bundle.main.path(forResource: phase.rawValue, ofType: "mp3")
|
||||
let url = NSURL(fileURLWithPath: filePath!)
|
||||
|
|
|
@ -162,7 +162,7 @@ struct TimerFormView: View {
|
|||
self.title = "New Timer"
|
||||
}
|
||||
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._lowDuration = State(initialValue: timer?.lowIntensityDuration ?? 30)
|
||||
self._highDuration = State(initialValue: timer?.highIntensityDuration ?? 60)
|
||||
|
|
|
@ -29,8 +29,8 @@ struct TimerListView: View {
|
|||
VStack(alignment: .leading) {
|
||||
Text(timer.name)
|
||||
.lineLimit(1)
|
||||
if timer.userDescription?.count ?? 0 > 0 {
|
||||
Text(timer.userDescription!)
|
||||
if timer.description?.count ?? 0 > 0 {
|
||||
Text(timer.description!)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
|
|
|
@ -30,5 +30,4 @@ class IntervalTimerTests: XCTestCase {
|
|||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue