import SwiftUI import CoreData struct ContentView: View { @EnvironmentObject var iapService: IAPService @EnvironmentObject var adMobService: AdMobService @EnvironmentObject var tabSelection: TabSelectionStore @EnvironmentObject var coreDataStack: CoreDataStack @AppStorage("onboardingCompleted") private var onboardingCompleted = false @AppStorage("faceIdEnabled") private var faceIdEnabled = false @AppStorage("pinEnabled") private var pinEnabled = false @AppStorage("lockOnLaunch") private var lockOnLaunch = true @AppStorage("lockOnBackground") private var lockOnBackground = false @Environment(\.scenePhase) private var scenePhase @State private var isUnlocked = false @State private var resolvedOnboardingCompleted: Bool? private var lockEnabled: Bool { faceIdEnabled || pinEnabled } var body: some View { ZStack { Group { if !isReadyForContent { AppLaunchLoadingView(messageKey: "loading_data") } else if resolvedOnboardingCompleted == false { OnboardingView(onboardingCompleted: $onboardingCompleted) } else { mainContent } } if onboardingCompleted && lockEnabled && !isUnlocked { AppLockView(isUnlocked: $isUnlocked) } } .onAppear { if !lockEnabled { isUnlocked = true } else { isUnlocked = !lockOnLaunch } } .onChange(of: lockEnabled) { _, enabled in if !enabled { isUnlocked = true } else { isUnlocked = !lockOnLaunch } } .onChange(of: onboardingCompleted) { _, completed in if completed && lockEnabled { isUnlocked = !lockOnLaunch } } .onChange(of: scenePhase) { _, phase in guard onboardingCompleted, lockEnabled else { return } if phase == .background && lockOnBackground { isUnlocked = false } } .onAppear { if coreDataStack.isLoaded { syncOnboardingState() } } .onChange(of: coreDataStack.isLoaded) { _, loaded in if loaded { syncOnboardingState() } } .onChange(of: onboardingCompleted) { _, completed in resolvedOnboardingCompleted = completed } } private var isReadyForContent: Bool { coreDataStack.isLoaded && resolvedOnboardingCompleted != nil } private func syncOnboardingState() { let settings = AppSettings.getOrCreate(in: coreDataStack.viewContext) var resolved = settings.onboardingCompleted || onboardingCompleted if !resolved && hasExistingData() { resolved = true } if settings.onboardingCompleted != resolved { settings.onboardingCompleted = resolved CoreDataStack.shared.save() } if onboardingCompleted != resolved { onboardingCompleted = resolved } resolvedOnboardingCompleted = resolved } private func hasExistingData() -> Bool { let context = coreDataStack.viewContext let sourceRequest: NSFetchRequest = InvestmentSource.fetchRequest() sourceRequest.fetchLimit = 1 sourceRequest.resultType = .countResultType if (try? context.count(for: sourceRequest)) ?? 0 > 0 { return true } let snapshotRequest: NSFetchRequest = Snapshot.fetchRequest() snapshotRequest.fetchLimit = 1 snapshotRequest.resultType = .countResultType if (try? context.count(for: snapshotRequest)) ?? 0 > 0 { return true } let accountRequest: NSFetchRequest = Account.fetchRequest() accountRequest.fetchLimit = 1 accountRequest.resultType = .countResultType return ((try? context.count(for: accountRequest)) ?? 0) > 0 } private var mainContent: some View { ZStack { TabView(selection: $tabSelection.selectedTab) { DashboardView() .tabItem { Label("Home", systemImage: "house.fill") } .tag(0) SourceListView(iapService: iapService) .tabItem { Label("Sources", systemImage: "list.bullet") } .tag(1) ChartsContainerView(iapService: iapService) .tabItem { Label("Charts", systemImage: "chart.xyaxis.line") } .tag(2) JournalView() .tabItem { Label("Journal", systemImage: "book.closed") } .tag(3) SettingsView(iapService: iapService) .tabItem { Label("Settings", systemImage: "gearshape.fill") } .tag(4) } } // Banner ad at bottom for free users .safeAreaInset(edge: .bottom, spacing: 0) { if !iapService.isPremium { BannerAdView() .frame(height: AppConstants.UI.bannerAdHeight) .frame(maxWidth: .infinity) .background(Color(.systemBackground)) } } } } #Preview { ContentView() .environmentObject(CoreDataStack.shared) .environmentObject(IAPService()) .environmentObject(AdMobService()) .environmentObject(AccountStore(iapService: IAPService())) .environmentObject(TabSelectionStore()) }