mycode/myApp/HaruDanim/IOS/ViewModels/TrackingViewModel.swift
2026-07-06 00:56:00 +09:00

86 lines
2.8 KiB
Swift

//
// TrackingViewModel.swift
// HaruDanim
//
// Drives real-time tracking of Actions: starting/stopping time sessions
// and recording count entries.
//
import Foundation
import SwiftData
import Observation
@Observable
final class TrackingViewModel {
/// Currently running time sessions (those without an `endTime`).
var activeSessions: [TimeSession] = []
// MARK: - Time tracking
/// Starts a new `TimeSession` for the action, or ends the running one if it is
/// already being tracked.
func toggleTimeTracking(for action: Action, context: ModelContext) {
if let running = runningSession(for: action, context: context) {
running.endTime = .now
} else {
let session = TimeSession(startTime: .now, action: action)
context.insert(session)
}
save(context)
refreshActiveSessions(context: context)
}
/// Ends a specific running session (used by the stop button in the UI).
func stopSession(_ session: TimeSession, context: ModelContext) {
guard session.endTime == nil else { return }
session.endTime = .now
save(context)
refreshActiveSessions(context: context)
}
// MARK: - Count tracking
/// Records a `CountEntry` for the action.
func incrementCount(for action: Action, amount: Int = 1, context: ModelContext) {
let entry = CountEntry(timestamp: .now, amount: amount, action: action)
context.insert(entry)
save(context)
}
// MARK: - Active session loading
/// Reloads `activeSessions` from the store (sessions with no `endTime`).
func refreshActiveSessions(context: ModelContext) {
let descriptor = FetchDescriptor<TimeSession>(
predicate: #Predicate { $0.endTime == nil },
sortBy: [SortDescriptor(\.startTime, order: .forward)]
)
activeSessions = (try? context.fetch(descriptor)) ?? []
}
/// Returns the running session belonging to `action`, if any.
func runningSession(for action: Action, context: ModelContext) -> TimeSession? {
let actionID = action.id
let descriptor = FetchDescriptor<TimeSession>(
predicate: #Predicate { $0.endTime == nil && $0.action?.id == actionID }
)
return try? context.fetch(descriptor).first
}
/// Whether the given action currently has a running session.
func isTracking(_ action: Action) -> Bool {
activeSessions.contains { $0.action?.id == action.id }
}
// MARK: - Persistence
private func save(_ context: ModelContext) {
do {
try context.save()
} catch {
// Persistence failures are non-fatal for the in-flight UI; surface in logs.
print("TrackingViewModel save failed: \(error)")
}
}
}