Migrate from Core Data to SwiftData
- Replace Core Data with SwiftData for iOS 18+ - Create MoodEntryModel as @Model class replacing MoodEntry entity - Create SharedModelContainer for App Group container sharing - Create DataController with CRUD extensions replacing PersistenceController - Update all views and view models to use MoodEntryModel - Update widget extension to use SwiftData - Remove old Core Data files (Persistence*.swift, .xcdatamodeld) - Add EntryType enum with all entry type cases - Fix widget label truncation with proper spacing and text scaling 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ struct Insight: Identifiable {
|
||||
let mood: Mood?
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class InsightsViewModel: ObservableObject {
|
||||
@Published var monthInsights: [Insight] = []
|
||||
@Published var yearInsights: [Insight] = []
|
||||
@@ -36,9 +37,9 @@ class InsightsViewModel: ObservableObject {
|
||||
let allTimeStart = Date(timeIntervalSince1970: 0)
|
||||
|
||||
// Fetch entries for each period
|
||||
let monthEntries = PersistenceController.shared.getData(startDate: monthStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
let yearEntries = PersistenceController.shared.getData(startDate: yearStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
let allTimeEntries = PersistenceController.shared.getData(startDate: allTimeStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
let monthEntries = DataController.shared.getData(startDate: monthStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
let yearEntries = DataController.shared.getData(startDate: yearStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
let allTimeEntries = DataController.shared.getData(startDate: allTimeStart, endDate: now, includedDays: [1,2,3,4,5,6,7])
|
||||
|
||||
// Generate insights for each period
|
||||
monthInsights = generateRandomInsights(entries: monthEntries, periodName: "this month", count: 5)
|
||||
@@ -46,7 +47,7 @@ class InsightsViewModel: ObservableObject {
|
||||
allTimeInsights = generateRandomInsights(entries: allTimeEntries, periodName: "all time", count: 5)
|
||||
}
|
||||
|
||||
private func generateRandomInsights(entries: [MoodEntry], periodName: String, count: Int) -> [Insight] {
|
||||
private func generateRandomInsights(entries: [MoodEntryModel], periodName: String, count: Int) -> [Insight] {
|
||||
// Filter out missing/placeholder entries
|
||||
let validEntries = entries.filter { ![.missing, .placeholder].contains($0.mood) }
|
||||
|
||||
@@ -85,7 +86,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Most Common Mood
|
||||
private func generateMostCommonMoodInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateMostCommonMoodInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
let moodCounts = Dictionary(grouping: entries, by: { $0.mood }).mapValues { $0.count }
|
||||
|
||||
@@ -120,7 +121,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Least Common Mood
|
||||
private func generateLeastCommonMoodInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateLeastCommonMoodInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
let moodCounts = Dictionary(grouping: entries, by: { $0.mood }).mapValues { $0.count }
|
||||
|
||||
@@ -143,7 +144,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Best Day of Week
|
||||
private func generateBestDayInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateBestDayInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
let weekdayCounts = Dictionary(grouping: entries, by: { Int($0.weekDay) })
|
||||
var weekdayScores: [Int: Float] = [:]
|
||||
@@ -176,7 +177,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Worst Day of Week
|
||||
private func generateWorstDayInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateWorstDayInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
let weekdayCounts = Dictionary(grouping: entries, by: { Int($0.weekDay) })
|
||||
var weekdayScores: [Int: Float] = [:]
|
||||
@@ -208,7 +209,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Streak Insights
|
||||
private func generateStreakInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateStreakInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
let streakInfo = calculateStreaks(entries: entries)
|
||||
|
||||
@@ -271,12 +272,12 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Trend Insights
|
||||
private func generateTrendInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateTrendInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
guard entries.count >= 4 else { return insights }
|
||||
|
||||
let sortedEntries = entries.sorted { $0.forDate! < $1.forDate! }
|
||||
let sortedEntries = entries.sorted { $0.forDate < $1.forDate }
|
||||
let halfCount = sortedEntries.count / 2
|
||||
|
||||
let firstHalf = Array(sortedEntries.prefix(halfCount))
|
||||
@@ -330,7 +331,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Positivity Insights
|
||||
private func generatePositivityInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generatePositivityInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let positiveDays = entries.filter { [.great, .good].contains($0.mood) }.count
|
||||
@@ -393,7 +394,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Weekend vs Weekday
|
||||
private func generateWeekendVsWeekdayInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateWeekendVsWeekdayInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let weekendDays = entries.filter { [1, 7].contains(Int($0.weekDay)) } // Sunday = 1, Saturday = 7
|
||||
@@ -431,12 +432,12 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Mood Swing Insights
|
||||
private func generateMoodSwingInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateMoodSwingInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
guard entries.count >= 3 else { return insights }
|
||||
|
||||
let sortedEntries = entries.sorted { $0.forDate! < $1.forDate! }
|
||||
let sortedEntries = entries.sorted { $0.forDate < $1.forDate }
|
||||
var swings = 0
|
||||
|
||||
for i in 1..<sortedEntries.count {
|
||||
@@ -483,7 +484,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Consistency Insights
|
||||
private func generateConsistencyInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateConsistencyInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let totalDaysInPeriod = calculateDaysInPeriod(periodName: periodName)
|
||||
@@ -535,7 +536,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Milestone Insights
|
||||
private func generateMilestoneInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateMilestoneInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let count = entries.count
|
||||
@@ -557,7 +558,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Comparative Insights
|
||||
private func generateComparativeInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateComparativeInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let moodCounts = Dictionary(grouping: entries, by: { $0.mood }).mapValues { $0.count }
|
||||
@@ -606,7 +607,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Pattern Insights
|
||||
private func generatePatternInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generatePatternInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
// Monday blues check
|
||||
@@ -662,7 +663,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Fun Fact Insights
|
||||
private func generateFunFactInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateFunFactInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let greatDays = entries.filter { $0.mood == .great }.count
|
||||
@@ -688,7 +689,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// Calculate "great day streak potential"
|
||||
let recentEntries = entries.sorted { $0.forDate! > $1.forDate! }.prefix(7)
|
||||
let recentEntries = entries.sorted { $0.forDate > $1.forDate }.prefix(7)
|
||||
let recentGreat = recentEntries.filter { $0.mood == .great }.count
|
||||
if recentGreat >= 3 {
|
||||
insights.append(Insight(
|
||||
@@ -712,7 +713,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Motivational Insights
|
||||
private func generateMotivationalInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateMotivationalInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let avgMood = Float(entries.reduce(0) { $0 + Int($1.moodValue) }) / Float(entries.count)
|
||||
@@ -760,7 +761,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Rare Mood Insights
|
||||
private func generateRareMoodInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateRareMoodInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let moodCounts = Dictionary(grouping: entries, by: { $0.mood }).mapValues { $0.count }
|
||||
@@ -800,13 +801,13 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Recent Insights
|
||||
private func generateRecentInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateRecentInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let sortedEntries = entries.sorted { $0.forDate! > $1.forDate! }
|
||||
let sortedEntries = entries.sorted { $0.forDate > $1.forDate }
|
||||
guard let mostRecent = sortedEntries.first else { return insights }
|
||||
|
||||
let daysSinceLastEntry = calendar.dateComponents([.day], from: mostRecent.forDate!, to: Date()).day ?? 0
|
||||
let daysSinceLastEntry = calendar.dateComponents([.day], from: mostRecent.forDate, to: Date()).day ?? 0
|
||||
|
||||
if daysSinceLastEntry == 0 {
|
||||
insights.append(Insight(
|
||||
@@ -858,13 +859,13 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Month of Year Insights
|
||||
private func generateMonthOfYearInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateMonthOfYearInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
guard periodName == "this year" || periodName == "all time" else { return insights }
|
||||
|
||||
let monthGroups = Dictionary(grouping: entries) { entry -> Int in
|
||||
calendar.component(.month, from: entry.forDate!)
|
||||
calendar.component(.month, from: entry.forDate)
|
||||
}
|
||||
|
||||
var monthScores: [Int: Float] = [:]
|
||||
@@ -899,7 +900,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Longest Mood Run
|
||||
private func generateLongestMoodRunInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateLongestMoodRunInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
for mood in [Mood.great, .good, .average, .bad, .horrible] {
|
||||
@@ -928,7 +929,7 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
// MARK: - Average Mood Insights
|
||||
private func generateAverageMoodInsights(entries: [MoodEntry], periodName: String) -> [Insight] {
|
||||
private func generateAverageMoodInsights(entries: [MoodEntryModel], periodName: String) -> [Insight] {
|
||||
var insights: [Insight] = []
|
||||
|
||||
let avgMood = Float(entries.reduce(0) { $0 + Int($1.moodValue) }) / Float(entries.count)
|
||||
@@ -980,8 +981,8 @@ class InsightsViewModel: ObservableObject {
|
||||
|
||||
// MARK: - Helper Functions
|
||||
|
||||
private func calculateStreaks(entries: [MoodEntry]) -> (currentStreak: Int, longestStreak: Int) {
|
||||
let sortedEntries = entries.sorted { $0.forDate! > $1.forDate! }
|
||||
private func calculateStreaks(entries: [MoodEntryModel]) -> (currentStreak: Int, longestStreak: Int) {
|
||||
let sortedEntries = entries.sorted { $0.forDate > $1.forDate }
|
||||
guard !sortedEntries.isEmpty else { return (0, 0) }
|
||||
|
||||
var currentStreak = 0
|
||||
@@ -989,14 +990,14 @@ class InsightsViewModel: ObservableObject {
|
||||
var tempStreak = 1
|
||||
|
||||
let today = calendar.startOfDay(for: Date())
|
||||
if let mostRecent = sortedEntries.first?.forDate,
|
||||
calendar.isDate(mostRecent, inSameDayAs: today) || calendar.isDate(mostRecent, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
|
||||
let mostRecent = sortedEntries.first!.forDate
|
||||
if calendar.isDate(mostRecent, inSameDayAs: today) || calendar.isDate(mostRecent, inSameDayAs: calendar.date(byAdding: .day, value: -1, to: today)!) {
|
||||
currentStreak = 1
|
||||
var checkDate = calendar.date(byAdding: .day, value: -1, to: mostRecent)!
|
||||
|
||||
for entry in sortedEntries.dropFirst() {
|
||||
if let entryDate = entry.forDate,
|
||||
calendar.isDate(entryDate, inSameDayAs: checkDate) {
|
||||
let entryDate = entry.forDate
|
||||
if calendar.isDate(entryDate, inSameDayAs: checkDate) {
|
||||
currentStreak += 1
|
||||
checkDate = calendar.date(byAdding: .day, value: -1, to: checkDate)!
|
||||
} else {
|
||||
@@ -1006,15 +1007,14 @@ class InsightsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
for i in 1..<sortedEntries.count {
|
||||
if let currentDate = sortedEntries[i].forDate,
|
||||
let previousDate = sortedEntries[i-1].forDate {
|
||||
let dayDiff = calendar.dateComponents([.day], from: currentDate, to: previousDate).day ?? 0
|
||||
if dayDiff == 1 {
|
||||
tempStreak += 1
|
||||
} else {
|
||||
longestStreak = max(longestStreak, tempStreak)
|
||||
tempStreak = 1
|
||||
}
|
||||
let currentDate = sortedEntries[i].forDate
|
||||
let previousDate = sortedEntries[i-1].forDate
|
||||
let dayDiff = calendar.dateComponents([.day], from: currentDate, to: previousDate).day ?? 0
|
||||
if dayDiff == 1 {
|
||||
tempStreak += 1
|
||||
} else {
|
||||
longestStreak = max(longestStreak, tempStreak)
|
||||
tempStreak = 1
|
||||
}
|
||||
}
|
||||
longestStreak = max(longestStreak, tempStreak)
|
||||
@@ -1022,8 +1022,8 @@ class InsightsViewModel: ObservableObject {
|
||||
return (currentStreak, longestStreak)
|
||||
}
|
||||
|
||||
private func calculateMoodStreaks(entries: [MoodEntry], moods: [Mood]) -> (current: Int, longest: Int) {
|
||||
let sortedEntries = entries.sorted { $0.forDate! < $1.forDate! }
|
||||
private func calculateMoodStreaks(entries: [MoodEntryModel], moods: [Mood]) -> (current: Int, longest: Int) {
|
||||
let sortedEntries = entries.sorted { $0.forDate < $1.forDate }
|
||||
guard !sortedEntries.isEmpty else { return (0, 0) }
|
||||
|
||||
var currentStreak = 0
|
||||
|
||||
Reference in New Issue
Block a user