import SwiftUI import CoreData /// Support for automated App Store screenshot capture. Activated by launching the app /// with the `--screenshots` argument (used by the UI-test capture flow). Only mutates /// state when that argument is present, so it never affects normal runs. enum ScreenshotMode { static var isActive: Bool { CommandLine.arguments.contains("--screenshots") } /// Skip onboarding, biometric/PIN lock and the What's New sheet so the app opens /// straight into content. Called very early, before ContentView reads @AppStorage. static func applyDefaultsIfNeeded() { guard isActive else { return } let d = UserDefaults.standard d.set(true, forKey: "onboardingCompleted") d.set(false, forKey: "faceIdEnabled") d.set(false, forKey: "pinEnabled") d.set(false, forKey: "lockOnLaunch") d.set(false, forKey: "lockOnBackground") let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" d.set(version, forKey: "lastSeenWhatsNewVersion") // Unlock premium (DEBUG-only override) so captures/UI tests can exercise // the premium charts without a StoreKit purchase. `--no-premium` keeps it // off to test the free-tier experience (locked charts, teasers). d.set(!CommandLine.arguments.contains("--no-premium"), forKey: "debugPremiumOverride") } /// Seed demo data (no-op if the store already has sources). Called once Core Data /// has finished loading so the view context is ready. static func seedIfNeeded() { guard isActive else { return } SampleDataService.shared.seedSampleData() makeCheckInPendingIfNeeded() } /// `--pending-checkin`: strip the seeded data back to a "month not done yet" /// state (recent snapshots + this month's journal completion removed) so the /// guided check-in flow can be exercised/captured. private static func makeCheckInPendingIfNeeded() { guard CommandLine.arguments.contains("--pending-checkin") else { return } let context = CoreDataStack.shared.viewContext let cutoff = Calendar.current.date(byAdding: .day, value: -40, to: Date()) ?? Date() let snapshotRequest = Snapshot.fetchRequest() snapshotRequest.predicate = NSPredicate(format: "date >= %@", cutoff as NSDate) (try? context.fetch(snapshotRequest))?.forEach(context.delete) let journalRequest = JournalEntry.fetchRequest() journalRequest.predicate = NSPredicate(format: "completionTime >= %@", cutoff as NSDate) (try? context.fetch(journalRequest))?.forEach(context.delete) try? context.save() SharedQuickUpdateSync.refreshMirror() } } @main struct PortfolioJournalApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var iapService: IAPService @StateObject private var adMobService: AdMobService @StateObject private var accountStore: AccountStore @StateObject private var tabSelection = TabSelectionStore() @Environment(\.scenePhase) private var scenePhase let coreDataStack = CoreDataStack.shared init() { // Screenshot/UI-capture mode: skip onboarding, lock and What's New so the app // launches straight into content with demo data (see ScreenshotMode). ScreenshotMode.applyDefaultsIfNeeded() // Clean up any duplicate objects from previous bugs before initializing stores CoreDataStack.shared.cleanupDuplicateObjects() let iap = IAPService() _iapService = StateObject(wrappedValue: iap) _adMobService = StateObject(wrappedValue: AdMobService()) _accountStore = StateObject(wrappedValue: AccountStore(iapService: iap)) } var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, coreDataStack.viewContext) .environmentObject(coreDataStack) .environmentObject(iapService) .environmentObject(adMobService) .environmentObject(accountStore) .environmentObject(tabSelection) } .onChange(of: scenePhase) { _, newPhase in if newPhase == .active { coreDataStack.refreshWidgetData() // Re-read all Core Data objects from the persistent store so that // iCloud changes made on other devices while this device was inactive // are reflected immediately without waiting for a remote-change notification. coreDataStack.refreshFromCloudKit() NotificationService.shared.scheduleReEngagementNotification() NotificationService.shared.scheduleMonthlyCheckIn() NotificationService.shared.scheduleStreakProtectionReminder() // Share-extension bridge: apply values captured from other apps and // refresh the source mirror the extension reads. if coreDataStack.isLoaded { SharedQuickUpdateSync.ingestPending() SharedQuickUpdateSync.refreshMirror() } } else if newPhase == .background { guard iapService.isPremium else { return } guard UserDefaults.standard.bool(forKey: "backupsEnabled") else { return } let retention = UserDefaults.standard.integer(forKey: "backupRetentionCount") let keepCount = [5, 10, 20].contains(retention) ? retention : 10 let includeICloud = UserDefaults.standard.bool(forKey: "cloudSyncEnabled") _ = BackupService.shared.createBackup( retentionCount: keepCount, includeICloud: includeICloud ) } } } }