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:
William Brawner 2020-11-05 18:51:51 -07:00
parent cedd5403c5
commit ead9b2ebe0
7 changed files with 88 additions and 31 deletions

View file

@ -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)
}
}

View file

@ -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 {

View file

@ -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"/>

View file

@ -183,20 +183,14 @@ 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)
}
let fetchedTimers = try self.persistentContainer.viewContext.fetch(fetchRequest) as! [IntervalTimerMO]
self.timers = .success(fetchedTimers.map { IntervalTimer.create(fromMO: $0) })
} catch {
DispatchQueue.main.async {
self.timers = .failure(.failed(error))
}
}
}
}
func saveTimer(
id: UUID? = nil,
@ -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]
timer = loadTimer(byId: uuid)!
} else {
timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer
}
} 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!)

View file

@ -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)

View file

@ -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)

View file

@ -30,5 +30,4 @@ class IntervalTimerTests: XCTestCase {
// Put the code you want to measure the time of here.
}
}
}