Files
FamilyMealPlanner/MealMood/ContentView.swift
T
alexandrev-tibco d8345a5b20 Revert "hogar compartido: colaboracion entre cuentas sobre Firestore"
El hogar compartido cambia donde viven los datos, asi que se va a la 3.0 y la
2.x queda para correcciones. El trabajo sigue vivo en la rama 3.0.0 (163fd60).

Importante para esta rama: esto tambien retira el entitlement
com.apple.developer.applesignin, que sin la capability marcada en el App ID
habria roto la firma al subir la 2.1.1 a TestFlight.

This reverts commit 163fd60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013su1ttRiMeMYxkZJ1Y3246
2026-09-12 13:08:27 +02:00

124 lines
4.7 KiB
Swift

import SwiftUI
import SwiftData
import StoreKit
struct ContentView: View {
@Environment(\.modelContext) private var context
@Environment(\.scenePhase) private var scenePhase
@Query private var allSettings: [AppSettings]
@State private var isReady = false
@State private var hasCompletedColdLaunch = false
private let premiumSyncService = PremiumSyncService()
private var settings: AppSettings? { allSettings.first }
var body: some View {
Group {
if !isReady {
ZStack {
Color.mealMoodBackground.ignoresSafeArea()
AppIconPlaceholder(size: 120)
}
} else if let settings = settings, settings.onboardingCompleted {
HomeView()
} else {
OnboardingView {
// Onboarding completed
}
}
}
.environment(\.locale, Locale(identifier: settings?.languageEnum.localeIdentifier ?? Locale.current.identifier))
// MARK: Transaction.updates listener (StoreKit 2 recommended pattern)
// Fires for new purchases, renewals and revocations while the app is running.
// Activate WatchConnectivity as early as possible so the first widget
// refresh doesn't race the session activation.
.onAppear { WatchSyncService.shared.activate() }
// This is the most reliable way to catch a purchase even if the app was
// interrupted during the payment flow.
.task {
for await result in Transaction.updates {
guard case .verified(let transaction) = result else { continue }
guard let s = settings else {
await transaction.finish()
continue
}
let isActive = transaction.revocationDate == nil &&
(transaction.expirationDate.map { $0 > Date() } ?? true)
if isActive && !s.isPremium {
s.isPremium = true
try? context.save()
} else if transaction.revocationDate != nil && s.isPremium {
// Explicitly revoked subscription downgrade
s.isPremium = false
try? context.save()
}
await transaction.finish()
}
}
.onAppear {
initializeAppIfNeeded()
Task {
if settings?.iCloudSyncEnabledResolved ?? true {
await ICloudSyncService.shared.pullRemoteIfNeeded(context: context)
}
await syncPremiumStatus(isColdLaunch: true)
hasCompletedColdLaunch = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
withAnimation(.easeInOut) {
isReady = true
}
}
}
.onChange(of: scenePhase) { _, phase in
Task {
switch phase {
case .active:
// Only run foreground sync after cold launch is complete.
// The first activation is covered by .onAppear above.
if hasCompletedColdLaunch {
await syncPremiumStatus(isColdLaunch: false)
}
case .inactive, .background:
if settings?.iCloudSyncEnabledResolved ?? true {
await ICloudSyncService.shared.pushLocalSnapshot(context: context)
}
@unknown default:
break
}
}
}
}
// MARK: - Private
private func initializeAppIfNeeded() {
// CloudKit mirroring can duplicate singletons/default data created on
// other devices collapse them before anything reads the store.
if CloudSyncRuntime.isCloudKitActive {
DeduplicationService.run(context: context)
}
if allSettings.isEmpty {
DefaultDataService.createDefaultSettings(context: context)
}
let tagDescriptor = FetchDescriptor<Tag>()
if (try? context.fetch(tagDescriptor))?.isEmpty ?? true {
DefaultDataService.createDefaultTags(context: context)
}
}
private func syncPremiumStatus(isColdLaunch: Bool) async {
guard let settings else { return }
let newPremium = await premiumSyncService.sync(
currentIsPremium: settings.isPremium,
isColdLaunch: isColdLaunch
)
if settings.isPremium != newPremium {
settings.isPremium = newPremium
try? context.save()
}
CrashlyticsService.setUserProperties(isPremium: settings.isPremium)
}
}