Files
Reflect/Shared/Views/DayView/DayViewViewModel.swift
Trey T 1f040ab676 v1.1 polish: accessibility, error logging, localization, and code quality sweep
- Wrap 30+ production print() statements in #if DEBUG guards across 18 files
- Add VoiceOver labels, hints, and traits to Watch app, Live Activities, widgets
- Add .accessibilityAddTraits(.isButton) to 15+ onTapGesture views
- Add text alternatives for color-only indicators (progress dots, mood circles)
- Localize raw string literals in NoteEditorView, EntryDetailView, widgets
- Replace 25+ silent try? with do/catch + AppLogger error logging
- Replace hardcoded font sizes with semantic Dynamic Type fonts
- Fix FIXME in IconPickerView (log icon change errors)
- Extract magic animation delays to named constants across 8 files
- Add widget empty state "Log your first mood!" messaging
- Hide decorative images from VoiceOver, add labels to ColorPickers
- Remove stale TODO in Color+Codable (alpha change deferred for migration)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 20:09:14 -05:00

118 lines
3.9 KiB
Swift

//
// ContentModeViewModel.swift
// Reflect (iOS)
//
// Created by Trey Tartt on 1/20/22.
//
import SwiftUI
import SwiftData
@MainActor
class DayViewViewModel: ObservableObject {
@Published var grouped = [Int: [Int: [MoodEntryModel]]]()
@Published var numberOfItems = 0
@Published var sortedGroupedData: [(year: Int, months: [(month: Int, entries: [MoodEntryModel])])] = []
let addMonthStartWeekdayPadding: Bool
var hasNoData: Bool {
grouped.isEmpty
}
private var numberOfEntries: Int {
Self.countEntries(in: grouped)
}
/// Count total entries across all year/month groups. Extracted for testability.
static func countEntries(in grouped: [Int: [Int: [MoodEntryModel]]]) -> Int {
grouped.values.flatMap(\.values).reduce(0) { $0 + $1.count }
}
private var dataListenerToken: DataController.DataListenerToken?
init(addMonthStartWeekdayPadding: Bool) {
self.addMonthStartWeekdayPadding = addMonthStartWeekdayPadding
dataListenerToken = DataController.shared.addNewDataListener { [weak self] in
guard let self = self else { return }
// Avoid withAnimation for bulk data updates - it causes expensive view diffing
self.updateData()
}
updateData()
}
deinit {
if let token = dataListenerToken {
Task { @MainActor in
DataController.shared.removeDataListener(token: token)
}
}
}
private func getGroupedData(addMonthStartWeekdayPadding: Bool) {
var newStuff = DataController.shared.splitIntoYearMonth(includedDays: [1,2,3,4,5,6,7])
if addMonthStartWeekdayPadding {
newStuff = MoodEntryFunctions.padMoodEntriesForCalendar(entries: newStuff)
}
grouped = newStuff
numberOfItems = numberOfEntries
}
public func updateData() {
getGroupedData(addMonthStartWeekdayPadding: self.addMonthStartWeekdayPadding)
sortedGroupedData = grouped
.sorted { $0.key > $1.key }
.map { (year: $0.key, months: $0.value.sorted { $0.key > $1.key }.map { (month: $0.key, entries: $0.value) }) }
}
public func add(mood: Mood, forDate date: Date, entryType: EntryType) {
MoodLogger.shared.logMood(mood, for: date, entryType: entryType)
}
public func update(entry: MoodEntryModel, toMood mood: Mood) {
if !MoodLogger.shared.updateMood(entryDate: entry.forDate, withMood: mood) {
#if DEBUG
print("Failed to update mood entry")
#endif
}
}
public func delete(offsets: IndexSet, inMonth month: Int, inYear year: Int) {
if let monthEntries = grouped[year],
let entries = monthEntries[month] {
var mutableEntries = entries.sorted(by: {
$0.forDate > $1.forDate
})
var entriesToDelete = [MoodEntryModel]()
for idx in offsets {
let obj = mutableEntries.remove(at: idx)
entriesToDelete.append(obj)
}
entriesToDelete.forEach({ entry in
MoodLogger.shared.deleteMood(forDate: entry.forDate)
})
}
}
static func updateTitleHeader(forEntry entry: MoodEntryModel?) -> String {
guard let entry = entry else {
return ""
}
let forDate = entry.forDate
let components = Calendar.current.dateComponents([.day, .month, .year], from: forDate)
let month = components.month ?? 1
let year = components.year ?? Calendar.current.component(.year, from: Date())
let monthName = Random.monthName(fromMonthInt: month)
let weekday = Random.weekdayName(fromDate: entry.forDate)
let dayz = Random.dayFormat(fromDate: entry.forDate)
let string = weekday + " " + monthName + " " + dayz + " " + String(year)
return String(format: String(localized: "content_view_fill_in_missing_entry"), string)
}
}