Files
Reflect/Shared/Persisence/DataControllerHelper.swift
Trey t 329fb7c671 Remove #if DEBUG guards for TestFlight, polish weekly digest and insights UX
- Remove #if DEBUG from all debug settings, exporters, and IAP bypass so
  debug options are available in TestFlight builds
- Weekly digest card: replace dismiss X with collapsible chevron caret
- Weekly digest: generate on-demand when opening Insights tab if no cached
  digest exists (BGTask + notification kept as bonus path)
- Fix digest intention text color (was .secondary, now uses theme textColor)
- Add "Generate Weekly Digest" debug button in Settings
- Add generating overlay on Insights tab with pulsing sparkles icon that
  stays visible until all sections finish loading (content at 0.2 opacity)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:15:23 -05:00

223 lines
7.7 KiB
Swift

//
// DataControllerHelper.swift
// Reflect
//
// SwiftData helper and test data operations.
//
import SwiftData
import Foundation
extension DataController {
func randomEntries(count: Int) -> [MoodEntryModel] {
var entries = [MoodEntryModel]()
for idx in 0..<count {
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
let entry = MoodEntryModel(
forDate: date,
mood: Mood.allValues.randomElement()!,
entryType: .listView
)
entry.timestamp = date
entries.append(entry)
}
return entries
}
func populateMemory() {
for idx in 1..<255 {
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
var moodValue = Int.random(in: 2...4)
if idx % 5 == 0 {
moodValue = Int.random(in: 0...4)
}
let entry = MoodEntryModel(
forDate: date,
mood: Mood(rawValue: moodValue) ?? .average,
entryType: .listView
)
entry.timestamp = date
modelContext.insert(entry)
}
save()
}
/// Creates an entry that is NOT inserted into the context - used for UI placeholders
func generateObjectNotInArray(forDate date: Date = Date(), withMood mood: Mood = .placeholder) -> MoodEntryModel {
let entry = MoodEntryModel(
forDate: date,
moodValue: mood.rawValue,
entryType: EntryType.listView.rawValue,
canEdit: false,
canDelete: false
)
return entry
}
func populateTestData() {
clearDB()
for idx in 1..<1000 {
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
let mood = Self.randomMood()
let entry = MoodEntryModel(
forDate: date,
mood: mood,
entryType: .listView,
notes: Self.randomNotes(),
weatherJSON: Self.randomWeatherJSON(for: date),
reflectionJSON: Self.randomReflectionJSON(for: mood, on: date)
)
modelContext.insert(entry)
}
saveAndRunDataListeners()
}
func populate2YearsData() {
clearDB()
for idx in 1...730 {
let date = Calendar.current.date(byAdding: .day, value: -idx, to: Date())!
let mood = Self.randomMood()
let entry = MoodEntryModel(
forDate: date,
mood: mood,
entryType: .listView,
notes: Self.randomNotes(),
weatherJSON: Self.randomWeatherJSON(for: date),
reflectionJSON: Self.randomReflectionJSON(for: mood, on: date)
)
modelContext.insert(entry)
}
saveAndRunDataListeners()
}
private static func randomMood() -> Mood {
var moodValue = Int.random(in: 3...4)
if Int.random(in: 0...400) % 5 == 0 {
moodValue = Int.random(in: 0...4)
}
return Mood(rawValue: moodValue) ?? .average
}
// ~40% of entries get notes
private static func randomNotes() -> String? {
guard Bool.random() && Bool.random() == false else { return nil }
return sampleNotes.randomElement()
}
private static let sampleNotes: [String] = [
"Had a productive morning and got a lot done.",
"Went for a long walk in the park today.",
"Feeling grateful for the little things.",
"Tough meeting at work but pushed through.",
"Spent quality time with family this evening.",
"Couldn't sleep well last night, feeling tired.",
"Great workout at the gym today!",
"Read a really interesting book chapter.",
"Cooked a new recipe and it turned out great.",
"Feeling a bit anxious about tomorrow.",
"Had coffee with an old friend, felt wonderful.",
"Rainy day but cozy inside with a good movie.",
"Got some bad news, trying to stay positive.",
"Meditation session helped clear my mind.",
"Busy day but managed to stay focused.",
"Enjoyed a quiet evening at home.",
"Felt overwhelmed by my to-do list.",
"Beautiful sunset on the drive home.",
"Tried journaling for the first time in a while.",
"Had a really meaningful conversation today.",
]
// ~25% of entries get guided reflections
private static func randomReflectionJSON(for mood: Mood, on date: Date) -> String? {
guard Int.random(in: 0...3) == 0 else { return nil }
let category = MoodCategory(from: mood)
let questions = GuidedReflection.questions(for: category)
let answers = sampleAnswers(for: category)
let responses = questions.enumerated().map { index, question in
GuidedReflection.Response(
id: index,
question: question,
answer: answers[index % answers.count]
)
}
let reflection = GuidedReflection(
moodCategory: category,
responses: responses,
completedAt: date
)
return reflection.encode()
}
private static func sampleAnswers(for category: MoodCategory) -> [String] {
switch category {
case .positive:
return [
"I felt a sense of calm and contentment throughout the day.",
"Connecting with a friend really lifted my spirits.",
"I want to keep making time for the things that bring me joy.",
]
case .neutral:
return [
"A mix of emotions today, nothing too strong in either direction.",
"Work was steady but unremarkable.",
"I'd like to be more intentional about how I spend my evenings.",
"Maybe adding a short walk after lunch could help.",
]
case .negative:
return [
"I've been worrying about things outside my control.",
"A stressful interaction this morning set the tone for the day.",
"I think I need some quiet time to recharge.",
"Taking a few deep breaths and stepping away from screens.",
]
}
}
private static let weatherConditions: [(symbol: String, condition: String)] = [
("sun.max.fill", "Clear"),
("cloud.sun.fill", "Partly Cloudy"),
("cloud.fill", "Cloudy"),
("cloud.rain.fill", "Rain"),
("cloud.heavyrain.fill", "Heavy Rain"),
("cloud.drizzle.fill", "Drizzle"),
("cloud.bolt.fill", "Thunderstorms"),
("cloud.snow.fill", "Snow"),
("cloud.fog.fill", "Foggy"),
("wind", "Windy"),
("sun.haze.fill", "Hazy"),
]
private static func randomWeatherJSON(for date: Date) -> String? {
let condition = weatherConditions.randomElement()!
let high = Double.random(in: 5...35)
let low = high - Double.random(in: 5...15)
let weather = WeatherData(
conditionSymbol: condition.symbol,
condition: condition.condition,
temperature: (high + low) / 2.0,
highTemperature: high,
lowTemperature: low,
humidity: Double.random(in: 0.2...0.95),
latitude: 37.7749,
longitude: -122.4194,
fetchedAt: date
)
return weather.encode()
}
func longestStreak() -> [MoodEntryModel] {
let descriptor = FetchDescriptor<MoodEntryModel>(
sortBy: [SortDescriptor(\.forDate, order: .forward)]
)
return (try? modelContext.fetch(descriptor)) ?? []
}
}