Migrate journal entries to CoreData for iCloud sync (build 33)
- Add JournalEntry CoreData entity (syncable=YES): id, monthKey, note, moodRaw, rating, completionTime, createdAt - Rewrite MonthlyCheckInStore: CoreData as primary storage, same public API - One-time migration from UserDefaults triggered after store loads - MonthlyCheckInCard: @FetchRequest on JournalEntry — reactive to iCloud sync - MonthlyCheckInView: onReceive NSManagedObjectContextObjectsDidChange to refresh @State vars on sync - Stats cards: observe NSManagedObjectContextObjectsDidChange instead of UserDefaults - ChartsViewModel.completedMonthKeys: remove MonthlyCheckInStore dependency (data-driven) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import Charts
|
||||
import CoreData
|
||||
|
||||
struct DashboardView: View {
|
||||
@EnvironmentObject var iapService: IAPService
|
||||
@@ -361,10 +362,7 @@ struct DashboardView: View {
|
||||
if config.isCollapsed {
|
||||
CompactCard(title: "Monthly Check-in", subtitle: "Last update: \(viewModel.formattedLastUpdate)")
|
||||
} else {
|
||||
MonthlyCheckInCard(
|
||||
lastUpdated: viewModel.formattedLastUpdate,
|
||||
lastUpdatedDate: viewModel.portfolioSummary.lastUpdated
|
||||
)
|
||||
MonthlyCheckInCard(lastUpdated: viewModel.formattedLastUpdate)
|
||||
}
|
||||
case .momentumStreaks:
|
||||
if config.isCollapsed {
|
||||
@@ -594,15 +592,16 @@ struct TotalValueCard: View {
|
||||
|
||||
struct MonthlyCheckInCard: View {
|
||||
let lastUpdated: String
|
||||
let lastUpdatedDate: Date?
|
||||
@State private var startDestinationActive = false
|
||||
@State private var cachedCompletionDate: Date?
|
||||
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \JournalEntry.completionTime, ascending: false)],
|
||||
predicate: NSPredicate(format: "completionTime != nil"),
|
||||
animation: .none
|
||||
) private var latestJournalEntry: FetchedResults<JournalEntry>
|
||||
|
||||
private var effectiveLastCheckInDate: Date? {
|
||||
// Use whichever is more recent: local completion record or latest snapshot date from CoreData.
|
||||
// cachedCompletionDate lives in UserDefaults (device-local, not iCloud-synced), so on a
|
||||
// secondary device after iCloud sync the snapshot date may be newer than the local record.
|
||||
[cachedCompletionDate, lastUpdatedDate].compactMap { $0 }.max()
|
||||
latestJournalEntry.first?.completionTime
|
||||
}
|
||||
|
||||
private var checkInProgress: Double {
|
||||
@@ -762,14 +761,6 @@ struct MonthlyCheckInCard: View {
|
||||
.background(Color(.systemBackground))
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
.onAppear {
|
||||
cachedCompletionDate = MonthlyCheckInStore.latestCompletionDate()
|
||||
}
|
||||
.onChange(of: startDestinationActive) { _, isActive in
|
||||
if !isActive {
|
||||
cachedCompletionDate = MonthlyCheckInStore.latestCompletionDate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var defaultReminderTime: Date {
|
||||
@@ -895,7 +886,7 @@ struct MomentumStreaksCard: View {
|
||||
.cornerRadius(AppConstants.UI.cornerRadius)
|
||||
.shadow(color: .black.opacity(0.05), radius: 8, y: 2)
|
||||
.onAppear(perform: refreshStats)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)) { _ in
|
||||
.onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)) { _ in
|
||||
refreshStats()
|
||||
}
|
||||
}
|
||||
@@ -968,7 +959,7 @@ struct MomentumStreaksCompactCard: View {
|
||||
subtitle: "Streak: \(stats.currentStreak)x • Best: \(stats.bestStreak)x"
|
||||
)
|
||||
.onAppear(perform: refreshStats)
|
||||
.onReceive(NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification)) { _ in
|
||||
.onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)) { _ in
|
||||
refreshStats()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,6 @@ struct MonthlyCheckInView: View {
|
||||
@Environment(\.openURL) private var openURL
|
||||
@EnvironmentObject var accountStore: AccountStore
|
||||
@StateObject private var viewModel = MonthlyCheckInViewModel()
|
||||
|
||||
// Provides the latest snapshot date from CoreData (synced via iCloud) as fallback
|
||||
// for lastCompletionDate when local MonthlyCheckInStore records are stale/missing.
|
||||
@FetchRequest(
|
||||
sortDescriptors: [NSSortDescriptor(keyPath: \Snapshot.date, ascending: false)],
|
||||
animation: .none
|
||||
) private var latestSnapshots: FetchedResults<Snapshot>
|
||||
@State private var referenceDate: Date
|
||||
|
||||
@State private var monthlyNote: String
|
||||
@@ -32,12 +25,7 @@ struct MonthlyCheckInView: View {
|
||||
}
|
||||
|
||||
private var lastCompletionDate: Date? {
|
||||
// MonthlyCheckInStore lives in UserDefaults (device-local, not iCloud-synced).
|
||||
// On a secondary device after iCloud sync the latest snapshot from CoreData may be
|
||||
// newer than the local check-in record. Use whichever is more recent.
|
||||
let localCompletion = MonthlyCheckInStore.latestCompletionDate()
|
||||
let latestSnapshotDate = latestSnapshots.first?.date
|
||||
return [localCompletion, latestSnapshotDate].compactMap { $0 }.max()
|
||||
MonthlyCheckInStore.latestCompletionDate()
|
||||
}
|
||||
|
||||
private var checkInProgress: Double {
|
||||
@@ -162,6 +150,11 @@ struct MonthlyCheckInView: View {
|
||||
starRating = MonthlyCheckInStore.rating(for: referenceDate) ?? 0
|
||||
selectedMood = MonthlyCheckInStore.mood(for: referenceDate)
|
||||
}
|
||||
.onReceive(NotificationCenter.default.publisher(for: .NSManagedObjectContextObjectsDidChange)) { _ in
|
||||
monthlyNote = MonthlyCheckInStore.note(for: referenceDate)
|
||||
starRating = MonthlyCheckInStore.rating(for: referenceDate) ?? 0
|
||||
selectedMood = MonthlyCheckInStore.mood(for: referenceDate)
|
||||
}
|
||||
.onReceive(accountStore.$selectedAccount) { account in
|
||||
viewModel.selectedAccount = account
|
||||
viewModel.selectedRange = DateRange.month(containing: referenceDate)
|
||||
|
||||
Reference in New Issue
Block a user