Fix iCloud sync: chart and next check-in date stale on secondary device (build 32)
- completedMonthKeys: remove UserDefaults-based MonthlyCheckInStore check (not synced via iCloud) - MonthlyCheckInCard.effectiveLastCheckInDate: use max() instead of ?? to prefer newer synced snapshot date - MonthlyCheckInView.lastCompletionDate: use max() of local + latest CoreData snapshot via @FetchRequest - FirebaseService.logPaywallShown: fix parameter key to paywall_trigger (matches GA4 custom dimension) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -459,7 +459,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournalDebug.entitlements;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -498,7 +498,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = PortfolioJournal/PortfolioJournal.entitlements;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_ASSET_PATHS = PortfolioJournal/Assets.xcassets;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -655,7 +655,7 @@
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
@@ -688,7 +688,7 @@
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = PortfolioJournalWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_ASSET_PATHS = PortfolioJournalWidget/Assets.xcassets;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = NO;
|
||||
@@ -719,7 +719,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
@@ -743,7 +743,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
@@ -766,7 +766,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
@@ -789,7 +789,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 31;
|
||||
CURRENT_PROJECT_VERSION = 32;
|
||||
DEVELOPMENT_TEAM = 2825Q76T7H;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
|
||||
|
||||
@@ -70,7 +70,7 @@ class FirebaseService {
|
||||
func logPaywallShown(trigger: String) {
|
||||
guard isConfigured else { return }
|
||||
Analytics.logEvent("paywall_shown", parameters: [
|
||||
"trigger": trigger
|
||||
"paywall_trigger": trigger
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -353,7 +353,10 @@ class DashboardViewModel: ObservableObject {
|
||||
for (key, monthSnapshots) in groupedByMonth {
|
||||
guard let monthDate = Calendar.current.date(from: key) else { continue }
|
||||
guard monthDate > cutoff else { continue }
|
||||
guard MonthlyCheckInStore.completionDate(for: monthDate) != nil else { continue }
|
||||
// A month is "complete" if all active sources have snapshot data for it.
|
||||
// We intentionally do NOT require a MonthlyCheckInStore entry here because
|
||||
// that store lives in UserDefaults (device-local) and is not synced via iCloud.
|
||||
// Snapshot data from CoreData is the authoritative cross-device source of truth.
|
||||
let monthSourceIds = Set(monthSnapshots.compactMap { $0.source?.id })
|
||||
if sourceIds.isSubset(of: monthSourceIds) {
|
||||
completed.insert(key)
|
||||
|
||||
@@ -599,7 +599,10 @@ struct MonthlyCheckInCard: View {
|
||||
@State private var cachedCompletionDate: Date?
|
||||
|
||||
private var effectiveLastCheckInDate: Date? {
|
||||
cachedCompletionDate ?? lastUpdatedDate
|
||||
// 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()
|
||||
}
|
||||
|
||||
private var checkInProgress: Double {
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
|
||||
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
|
||||
@@ -24,7 +32,12 @@ struct MonthlyCheckInView: View {
|
||||
}
|
||||
|
||||
private var lastCompletionDate: Date? {
|
||||
MonthlyCheckInStore.latestCompletionDate()
|
||||
// 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()
|
||||
}
|
||||
|
||||
private var checkInProgress: Double {
|
||||
|
||||
Reference in New Issue
Block a user