10f6d0ca20
iCloud sync: - Force viewContext.refreshAllObjects() on remote change notifications so data from other devices is picked up immediately without app restart - Call refreshFromCloudKit() on foreground to merge any changes made while the device was inactive - Wait up to 10s for initial CloudKit sync on launch before showing onboarding (shows "Checking iCloud..." during the wait) - New OnboardingICloudCheckView: shown on fresh installs with iCloud available, lets user restore from iCloud before starting onboarding from scratch Localization: - Added de, fr, it, ja, pt-BR lproj folders - New iCloud onboarding strings in en + es-ES (+ button literals) ASO metadata (fastlane): - Updated en-US: new subtitle, keywords, description (fixed "no cloud sync" claim), promotional text, release notes - Added full metadata for es-ES, de-DE, fr-FR, it, ja, pt-BR (63 files total) - All keyword fields validated ≤100 Unicode chars Infrastructure: - Gemfile + Gemfile.lock for fastlane - Scripts/archive_and_upload_appstore.sh for CI/CD Version: 1.2.1 (build 7) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
55 lines
2.3 KiB
Swift
55 lines
2.3 KiB
Swift
import SwiftUI
|
|
|
|
@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() {
|
|
// 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()
|
|
} 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
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|