Files
Reflect/Shared/Persisence/DataControllerADD.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

125 lines
4.4 KiB
Swift

//
// DataControllerADD.swift
// Reflect
//
// SwiftData CREATE operations.
//
import SwiftData
import Foundation
import os.log
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.88oakapps.reflect", category: "DataControllerADD")
extension DataController {
func add(mood: Mood, forDate date: Date, entryType: EntryType) {
// Delete ALL existing entries for this date (handles duplicates)
let existing = getAllEntries(byDate: date)
for entry in existing {
cleanupPhotoIfNeeded(for: entry)
modelContext.delete(entry)
}
if !existing.isEmpty {
do {
try modelContext.save()
} catch {
logger.error("Failed to save after deleting existing entries: \(error.localizedDescription)")
}
}
let entry = MoodEntryModel(
forDate: date,
mood: mood,
entryType: entryType
)
modelContext.insert(entry)
AnalyticsManager.shared.track(.moodLogged(mood: mood.rawValue, entryType: String(describing: entryType)))
saveAndRunDataListeners()
}
func fillInMissingDates() {
let currentOnboarding = UserDefaultsStore.getOnboarding()
var endDate = ShowBasedOnVoteLogics.getCurrentVotingDate(onboardingData: currentOnboarding)
// Since it's for views, take away the last date so vote is enabled
guard let adjustedEndDate = Calendar.current.date(byAdding: .day, value: -1, to: endDate) else {
logger.error("Failed to calculate adjusted end date")
return
}
endDate = adjustedEndDate
let descriptor = FetchDescriptor<MoodEntryModel>(
sortBy: [SortDescriptor(\.forDate, order: .reverse)]
)
let entries: [MoodEntryModel]
do {
entries = try modelContext.fetch(descriptor)
} catch {
logger.error("Failed to fetch entries for fill-in: \(error.localizedDescription)")
return
}
guard let firstEntry = entries.last else { return }
let allDates: [Date] = Date.dates(from: firstEntry.forDate, toDate: endDate, includingToDate: true).compactMap {
Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0)
}
let existingDates: Set<Date> = Set(entries.compactMap {
Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0.forDate)
})
let missing = Array(Set(allDates).subtracting(existingDates)).sorted(by: >)
guard !missing.isEmpty else { return }
// Batch insert all missing dates without triggering listeners
for date in missing {
// Add 12 hours to avoid UTC offset issues
guard let adjustedDate = Calendar.current.date(byAdding: .hour, value: 12, to: date) else {
logger.error("Failed to calculate adjusted date for \(date)")
continue
}
let entry = MoodEntryModel(
forDate: adjustedDate,
mood: .missing,
entryType: .filledInMissing
)
modelContext.insert(entry)
}
// Single save and listener notification at the end
saveAndRunDataListeners()
AnalyticsManager.shared.track(.missingEntriesFilled(count: missing.count))
}
func fixWrongWeekdays() {
let data = getData(startDate: Date(timeIntervalSince1970: 0), endDate: Date(), includedDays: [])
for entry in data {
entry.weekDay = Calendar.current.component(.weekday, from: entry.forDate)
}
saveAndRunDataListeners()
}
func removeNoForDates() {
// Note: With SwiftData's non-optional forDate, this is essentially a no-op
// Keeping for API compatibility
}
/// Batch insert mood entries without per-entry analytics or listener notifications.
/// Used for CSV import where side effects should fire once at the end.
func addBatch(entries: [(mood: Mood, date: Date, entryType: EntryType)]) {
for (mood, date, entryType) in entries {
let existing = getAllEntries(byDate: date)
for entry in existing {
cleanupPhotoIfNeeded(for: entry)
modelContext.delete(entry)
}
let entry = MoodEntryModel(forDate: date, mood: mood, entryType: entryType)
modelContext.insert(entry)
}
saveAndRunDataListeners()
}
}