interval-timer-ios/IntervalTimer/TimerDataStore.swift

236 lines
7.6 KiB
Swift
Raw Normal View History

//
// TimerDataStore.swift
// IntervalTimer
//
// Created by William Brawner on 10/23/20.
// Copyright © 2020 William Brawner. All rights reserved.
//
import Combine
import CoreData
import Foundation
import SwiftUI
class TimerDataStore: ObservableObject {
private var persistentContainer: NSPersistentContainer
private let internalTimer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
private var timerCancellable: AnyCancellable? = nil
@Published var activeTimer: ActiveTimerState? = nil {
didSet {
self.hasActiveTimer = self.activeTimer != nil
}
}
@Published var hasActiveTimer: Bool = false
@Published var timers: Result<[IntervalTimer], TimerError> = .failure(.loading)
func openTimer(_ timer: IntervalTimer) {
self.activeTimer = ActiveTimerState(
timer: timer,
timeRemaining: timer.warmUpDuration,
currentSet: timer.sets,
currentRound: timer.rounds,
soundId: nil,
phase: Phase.warmUp,
isRunning: false
)
}
func closeTimer() {
self.activeTimer = nil
}
func goBack() {
guard let state = self.activeTimer else {
return
}
switch state.phase {
case .warmUp:
self.activeTimer = state.copy(
timeRemaining: state.timer.warmUpDuration
)
case .low:
if state.currentSet == state.timer.sets && state.currentRound == state.timer.rounds {
self.activeTimer = state.copy(
timeRemaining: state.timer.warmUpDuration,
phase: .warmUp
)
} else if state.currentSet == state.timer.sets && state.currentRound < state.timer.rounds {
self.activeTimer = state.copy(
timeRemaining: state.timer.restDuration,
phase: .rest
)
} else {
self.activeTimer = state.copy(
timeRemaining: state.timer.highIntensityDuration,
currentSet: state.currentSet + 1,
phase: .high
)
}
case .high:
self.activeTimer = state.copy(
timeRemaining: state.timer.lowIntensityDuration,
phase: .low
)
case .rest:
self.activeTimer = state.copy(
timeRemaining: state.timer.highIntensityDuration,
phase: .high
)
case .cooldown:
self.activeTimer = state.copy(
timeRemaining: state.timer.highIntensityDuration,
phase: .high
)
}
}
func toggle() {
guard let state = self.activeTimer else {
return
}
if self.timerCancellable != nil {
self.timerCancellable?.cancel()
self.timerCancellable = nil
} else {
self.timerCancellable = self.internalTimer.sink(receiveValue: { _ in
self.updateTimer()
})
}
self.activeTimer = state.copy(isRunning: self.timerCancellable != nil)
UIApplication.shared.isIdleTimerDisabled = self.activeTimer?.isRunning ?? false
}
func updateTimer() {
guard let state = self.activeTimer else {
return
}
let newState = state.copy(timeRemaining: state.timeRemaining - 1)
if newState.timeRemaining == 0 {
goForward()
} else {
self.activeTimer = newState
}
}
func goForward() {
guard let state = self.activeTimer else {
return
}
switch state.phase {
case .warmUp:
self.activeTimer = state.copy(
timeRemaining: state.timer.lowIntensityDuration,
phase: .low
)
case .low:
self.activeTimer = state.copy(
timeRemaining: state.timer.highIntensityDuration,
phase: .high
)
case .high:
if state.currentSet > 1 {
self.activeTimer = state.copy(
timeRemaining: state.timer.lowIntensityDuration,
currentSet: state.currentSet - 1,
phase: .low
)
} else if state.currentRound > 1 {
self.activeTimer = state.copy(
timeRemaining: state.timer.restDuration,
currentRound: state.currentRound - 1,
phase: .rest
)
} else {
self.activeTimer = state.copy(
timeRemaining: state.timer.cooldownDuration,
phase: .cooldown
)
}
case .rest:
self.activeTimer = state.copy(
timeRemaining: state.timer.lowIntensityDuration,
currentSet: state.timer.sets,
phase: .low
)
case .cooldown:
self.activeTimer = state.copy(
timeRemaining: 0,
isRunning: false
)
self.timerCancellable?.cancel()
self.timerCancellable = nil
UIApplication.shared.isIdleTimerDisabled = false
}
}
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))
}
}
}
}
func saveTimer(
id: UUID? = nil,
name: String,
description: String? = nil,
warmUpDuration: Int64,
lowIntensityDuration: Int64,
highIntensityDuration: Int64,
restDuration: Int64,
cooldownDuration: Int64,
sets: Int64,
rounds: Int64
) {
let timer = IntervalTimer.init(entity: NSEntityDescription.entity(forEntityName: "IntervalTimer", in: persistentContainer.viewContext)!, insertInto: persistentContainer.viewContext) as IntervalTimer
timer.id = id ?? UUID()
timer.name = name
timer.userDescription = description
timer.warmUpDuration = warmUpDuration
timer.lowIntensityDuration = lowIntensityDuration
timer.highIntensityDuration = highIntensityDuration
timer.restDuration = restDuration
timer.cooldownDuration = cooldownDuration
timer.sets = sets
timer.rounds = rounds
let viewContext = persistentContainer.viewContext
viewContext.insert(timer)
try! viewContext.save()
loadTimers()
}
func deleteTimer(_ timer: IntervalTimer) {
let viewContext = persistentContainer.viewContext
viewContext.delete(timer)
try! viewContext.save()
loadTimers()
}
init(_ completionClosure: @escaping () -> ()) {
persistentContainer = NSPersistentContainer(name: "IntervalTimer")
persistentContainer.loadPersistentStores() { (description, error) in
if let error = error {
fatalError("Failed to load Core Data stack: \(error)")
}
self.loadTimers()
completionClosure()
}
}
}
enum TimerError: Error {
case loading
case failed(_ error: Error)
}