// // 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) } }