mycode/IOS/ViewModels/GoalViewModel.swift
2026-06-18 22:16:28 +09:00

94 lines
3.0 KiB
Swift

import Foundation
import Observation
import SwiftData
enum GoalTargetType: String, CaseIterable {
case task
case category
}
struct GoalProgress: Identifiable {
var id: UUID { goal.id }
let goal: Goal
let current: Double
let target: Double
var ratio: Double { target > 0 ? min(current / target, 1.0) : 0.0 }
var isCompleted: Bool { current >= target }
var displayName: String {
if let task = goal.task { return "\(task.icon) \(task.name)" }
if let category = goal.category { return category.name }
return ""
}
var percentText: String { "\(Int(ratio * 100))%" }
}
@MainActor
@Observable
final class GoalViewModel {
private(set) var goals: [Goal] = []
private(set) var goalProgress: [GoalProgress] = []
private var context: ModelContext?
func setup(context: ModelContext) {
self.context = context
fetchGoals()
}
func fetchGoals() {
guard let context else { return }
let descriptor = FetchDescriptor<Goal>()
goals = (try? context.fetch(descriptor)) ?? []
computeProgress()
}
func addGoal(targetType: String, conditions: Double, frequency: GoalFrequency, task: TaskItem?, category: Category?) {
guard let context else { return }
let goal = Goal(targetType: targetType, conditions: conditions, frequency: frequency, task: task, category: category)
context.insert(goal)
try? context.save()
fetchGoals()
}
func deleteGoal(_ goal: Goal) {
guard let context else { return }
context.delete(goal)
try? context.save()
fetchGoals()
}
private func computeProgress() {
let now = Date.now
let cal = Calendar.current
goalProgress = goals.map { goal in
let logs = relevantLogs(for: goal, reference: now, calendar: cal)
let current: Double = goal.targetType == "count"
? Double(logs.reduce(0) { $0 + $1.count })
: logs.reduce(0.0) { $0 + $1.duration }
return GoalProgress(goal: goal, current: current, target: goal.conditions)
}
}
private func relevantLogs(for goal: Goal, reference: Date, calendar: Calendar) -> [TaskLog] {
let allLogs: [TaskLog]
if let task = goal.task {
allLogs = task.logs
} else if let category = goal.category {
allLogs = category.tasks.flatMap { $0.logs }
} else {
return []
}
return allLogs.filter { inPeriod($0.date, frequency: goal.frequency, reference: reference, calendar: calendar) }
}
private func inPeriod(_ date: Date, frequency: GoalFrequency, reference: Date, calendar: Calendar) -> Bool {
switch frequency {
case .daily: return calendar.isDate(date, inSameDayAs: reference)
case .weekly: return calendar.isDate(date, equalTo: reference, toGranularity: .weekOfYear)
case .monthly: return calendar.isDate(date, equalTo: reference, toGranularity: .month)
}
}
}