persist stopwatches
This commit is contained in:
@@ -11,14 +11,22 @@ import Combine
|
|||||||
class ContentViewModel: ObservableObject {
|
class ContentViewModel: ObservableObject {
|
||||||
@Published var stopwatches: [Stopwatch] = []
|
@Published var stopwatches: [Stopwatch] = []
|
||||||
|
|
||||||
|
private let saveKey = "SavedStopwatches"
|
||||||
|
|
||||||
|
init() {
|
||||||
|
load()
|
||||||
|
}
|
||||||
|
|
||||||
func addStopwatch(name: String) {
|
func addStopwatch(name: String) {
|
||||||
let newStopwatch = Stopwatch(name: name)
|
let newStopwatch = Stopwatch(name: name)
|
||||||
stopwatches.append(newStopwatch)
|
stopwatches.append(newStopwatch)
|
||||||
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteStopwatch(at index: Int) {
|
func deleteStopwatch(at index: Int) {
|
||||||
stopwatches[index].pause() // Ensure timer is stopped
|
stopwatches[index].pause() // Ensure timer is stopped
|
||||||
stopwatches.remove(at: index)
|
stopwatches.remove(at: index)
|
||||||
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteStopwatch(id: UUID) {
|
func deleteStopwatch(id: UUID) {
|
||||||
@@ -26,11 +34,26 @@ class ContentViewModel: ObservableObject {
|
|||||||
deleteStopwatch(at: index)
|
deleteStopwatch(at: index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func save() {
|
||||||
|
if let encoded = try? JSONEncoder().encode(stopwatches) {
|
||||||
|
UserDefaults.standard.set(encoded, forKey: saveKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
if let data = UserDefaults.standard.data(forKey: saveKey) {
|
||||||
|
if let decoded = try? JSONDecoder().decode([Stopwatch].self, from: data) {
|
||||||
|
stopwatches = decoded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
@StateObject private var viewModel = ContentViewModel()
|
@StateObject private var viewModel = ContentViewModel()
|
||||||
@State private var isShowingAddSheet = false
|
@State private var isShowingAddSheet = false
|
||||||
|
@Environment(\.scenePhase) private var scenePhase
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
@@ -73,6 +96,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: scenePhase) { newPhase in
|
||||||
|
if newPhase == .background || newPhase == .inactive {
|
||||||
|
viewModel.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
|
|
||||||
class Stopwatch: ObservableObject, Identifiable {
|
class Stopwatch: ObservableObject, Identifiable, Codable {
|
||||||
let id = UUID()
|
let id: UUID
|
||||||
@Published var name: String
|
@Published var name: String
|
||||||
@Published var elapsedTime: TimeInterval = 0
|
@Published var elapsedTime: TimeInterval = 0
|
||||||
@Published var isRunning: Bool = false
|
@Published var isRunning: Bool = false
|
||||||
@@ -18,10 +18,44 @@ class Stopwatch: ObservableObject, Identifiable {
|
|||||||
private var startTime: Date?
|
private var startTime: Date?
|
||||||
private var accumulatedTime: TimeInterval = 0
|
private var accumulatedTime: TimeInterval = 0
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case id, name, elapsedTime, isRunning, startTime, accumulatedTime
|
||||||
|
}
|
||||||
|
|
||||||
init(name: String) {
|
init(name: String) {
|
||||||
|
self.id = UUID()
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
required init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
id = try container.decode(UUID.self, forKey: .id)
|
||||||
|
name = try container.decode(String.self, forKey: .name)
|
||||||
|
elapsedTime = try container.decode(TimeInterval.self, forKey: .elapsedTime)
|
||||||
|
isRunning = try container.decode(Bool.self, forKey: .isRunning)
|
||||||
|
startTime = try container.decodeIfPresent(Date.self, forKey: .startTime)
|
||||||
|
accumulatedTime = try container.decode(TimeInterval.self, forKey: .accumulatedTime)
|
||||||
|
|
||||||
|
if isRunning {
|
||||||
|
// Restart the timer if it was running.
|
||||||
|
// We also need to update current elapsedTime based on how much time passed since startTime
|
||||||
|
if let start = startTime {
|
||||||
|
elapsedTime = accumulatedTime + Date().timeIntervalSince(start)
|
||||||
|
}
|
||||||
|
startTimer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(id, forKey: .id)
|
||||||
|
try container.encode(name, forKey: .name)
|
||||||
|
try container.encode(elapsedTime, forKey: .elapsedTime)
|
||||||
|
try container.encode(isRunning, forKey: .isRunning)
|
||||||
|
try container.encode(startTime, forKey: .startTime)
|
||||||
|
try container.encode(accumulatedTime, forKey: .accumulatedTime)
|
||||||
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
timer?.cancel()
|
timer?.cancel()
|
||||||
}
|
}
|
||||||
@@ -31,8 +65,11 @@ class Stopwatch: ObservableObject, Identifiable {
|
|||||||
|
|
||||||
startTime = Date()
|
startTime = Date()
|
||||||
isRunning = true
|
isRunning = true
|
||||||
|
startTimer()
|
||||||
timer = Timer.publish(every: 0.1, on: .main, in: .common)
|
}
|
||||||
|
|
||||||
|
private func startTimer() {
|
||||||
|
timer = Timer.publish(every: 0.01, on: .main, in: .common)
|
||||||
.autoconnect()
|
.autoconnect()
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
self?.tick()
|
self?.tick()
|
||||||
@@ -71,12 +108,12 @@ class Stopwatch: ObservableObject, Identifiable {
|
|||||||
let hours = Int(elapsedTime) / 3600
|
let hours = Int(elapsedTime) / 3600
|
||||||
let minutes = Int(elapsedTime) / 60 % 60
|
let minutes = Int(elapsedTime) / 60 % 60
|
||||||
let seconds = Int(elapsedTime) % 60
|
let seconds = Int(elapsedTime) % 60
|
||||||
let tenths = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 10)
|
let tenths = Int((elapsedTime.truncatingRemainder(dividingBy: 1)) * 100)
|
||||||
|
|
||||||
if hours > 0 {
|
if hours > 0 {
|
||||||
return String(format: "%02i:%02i:%02i.%01i", hours, minutes, seconds, tenths)
|
return String(format: "%02i:%02i:%02i.%02i", hours, minutes, seconds, tenths)
|
||||||
} else {
|
} else {
|
||||||
return String(format: "%02i:%02i.%01i", minutes, seconds, tenths)
|
return String(format: "%02i:%02i.%02i", minutes, seconds, tenths)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ struct StopwatchRow: View {
|
|||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
|
||||||
Button(action: onDelete) {
|
Button(action: onDelete) {
|
||||||
Image(systemName: "trash")
|
Image(systemName: "trash.circle.fill")
|
||||||
.resizable()
|
.resizable()
|
||||||
.frame(width: 24, height: 24)
|
.frame(width: 44, height: 44)
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
|||||||
Reference in New Issue
Block a user