import SwiftUI struct FrontPageView: View { @EnvironmentObject private var subscriptions: SubscriptionManager @EnvironmentObject private var appRating: AppRatingManager @Environment(AppearanceManager.self) private var appearanceManager @Bindable var session: AppSessionState @Bindable private var freeUsageManager = AIFreeUsageManager.shared private var viewModel: FrontPageViewModel { session.frontPage } private var paywallViewModel: PaywallViewModel { session.paywall } private var postGeneratorViewModel: PostGeneratorViewModel { session.postGenerator } private var titleOptimizerViewModel: TitleOptimizerViewModel { session.titleOptimizer } private var commentWriterViewModel: CommentWriterViewModel { session.commentWriter } private var settingsViewModel: SettingsViewModel { session.settings } private var historyViewModel: AIHistoryViewModel { session.history } var body: some View { let _ = appearanceManager.isDarkMode ZStack { HStack(spacing: 0) { SidebarView( viewModel: viewModel, isPremium: subscriptions.hasPremiumAccess ) Rectangle() .fill(AppTheme.border) .frame(width: 1) mainContent .frame(maxWidth: .infinity, maxHeight: .infinity) } .fullScreenLayoutWhenNeeded() .environment(\.requirePremiumAccess, { if subscriptions.isResolvingEntitlements { return false } if subscriptions.hasPremiumAccess { return true } viewModel.showPaywall() return false }) if viewModel.isPaywallPresented { PaywallView(viewModel: paywallViewModel) { viewModel.dismissPaywall() } .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea() .zIndex(1) .transition(.opacity) } } .animation(.easeInOut(duration: 0.2), value: viewModel.isPaywallPresented) .frame(maxWidth: .infinity, maxHeight: .infinity) .frame(minWidth: AppWindow.minWidth, minHeight: AppWindow.minHeight) .onChange(of: subscriptions.hasPremiumAccess) { _, hasPremium in viewModel.dismissPaywall() syncToolSubscriptionState() appRating.updatePremiumStatus(hasPremium) if subscriptions.hasResolvedPremiumStatus { configureRedditAccessForPremiumStatus() } } .onChange(of: subscriptions.hasEverPurchasedPremium) { _, _ in syncToolSubscriptionState() } .onChange(of: subscriptions.hasResolvedPremiumStatus) { _, resolved in if resolved { syncToolSubscriptionState() configureRedditAccessForPremiumStatus() } } .onChange(of: freeUsageManager.usedCount) { _, _ in syncToolSubscriptionState() } .onAppear { syncToolSubscriptionState() configureToolPaywallHandlers() appRating.updatePremiumStatus(subscriptions.hasPremiumAccess) if subscriptions.hasResolvedPremiumStatus { configureRedditAccessForPremiumStatus() } } .background(AppTheme.background) .alert("Sign-in issue", isPresented: authErrorBinding) { Button("OK") { viewModel.clearAuthError() } } message: { Text(viewModel.authErrorMessage ?? "") } } private var authErrorBinding: Binding { Binding( get: { viewModel.authErrorMessage != nil }, set: { isPresented in if !isPresented { viewModel.clearAuthError() } } ) } @ViewBuilder private var mainContent: some View { VStack(spacing: 0) { if viewModel.isRedditActive { AppTheme.sidebarBackground .frame(height: AppWindow.redditTopInset) .frame(maxWidth: .infinity) .allowsHitTesting(false) } if viewModel.isRedditActive, viewModel.redditCanGoBack { RedditWebBackBar( canGoBack: true, onBack: { viewModel.goBackInReddit() } ) } ZStack { if viewModel.isRedditActive || viewModel.hasOpenedReddit { RedditWebView( url: viewModel.redditLoadURL, reloadTrigger: viewModel.redditReloadTrigger, isDarkMode: appearanceManager.isDarkMode, goBackTrigger: viewModel.redditGoBackTrigger, onCanGoBackChange: { viewModel.updateRedditCanGoBack($0) }, onAuthError: { viewModel.showAuthError($0) } ) .id("reddit-webview") .opacity(viewModel.isRedditActive ? 1 : 0) .allowsHitTesting(viewModel.isRedditActive) .accessibilityHidden(!viewModel.isRedditActive) .frame(maxWidth: .infinity, maxHeight: .infinity) } if viewModel.isSettingsActive { SettingsView(viewModel: settingsViewModel) } else if viewModel.isHistoryActive { AIHistoryView(viewModel: historyViewModel) { entry in openHistoryEntry(entry) } } else if !viewModel.isRedditActive, let item = viewModel.selectedNavItem { toolContent(for: item) } } } .background(viewModel.isRedditActive ? AppTheme.sidebarBackground : AppTheme.background) .frame(maxWidth: .infinity, maxHeight: .infinity) .ignoresSafeArea(.container, edges: viewModel.isRedditActive ? .top : []) } @ViewBuilder private func toolContent(for item: SidebarNavItem) -> some View { switch item.iconKind { case .postGenerator: PostGeneratorView(viewModel: postGeneratorViewModel) case .titleOptimizer: TitleOptimizerView(viewModel: titleOptimizerViewModel) case .commentWriter: CommentWriterView(viewModel: commentWriterViewModel) default: ToolPlaceholderView(item: item) } } private func syncToolSubscriptionState() { let hasPremium = subscriptions.hasPremiumAccess let hasEverPurchased = subscriptions.hasEverPurchasedPremium postGeneratorViewModel.setSubscriptionState( hasPremiumAccess: hasPremium, hasEverPurchasedPremium: hasEverPurchased ) titleOptimizerViewModel.setSubscriptionState( hasPremiumAccess: hasPremium, hasEverPurchasedPremium: hasEverPurchased ) commentWriterViewModel.setSubscriptionState( hasPremiumAccess: hasPremium, hasEverPurchasedPremium: hasEverPurchased ) } private func configureToolPaywallHandlers() { let showPaywall = { [viewModel] in viewModel.showPaywall() } postGeneratorViewModel.onPaywallRequired = showPaywall titleOptimizerViewModel.onPaywallRequired = showPaywall commentWriterViewModel.onPaywallRequired = showPaywall } private func configureRedditAccessForPremiumStatus() { guard subscriptions.hasResolvedPremiumStatus else { return } if !subscriptions.hasPremiumAccess, viewModel.isRedditActive { viewModel.isRedditActive = false viewModel.selectNavItem(FrontPageViewModel.postGeneratorItem) } } private func openHistoryEntry(_ entry: AIHistoryEntry) { switch entry.toolKind { case .postGenerator: if let item = viewModel.createItems.first(where: { $0.iconKind == .postGenerator }) { postGeneratorViewModel.restore(from: entry) viewModel.selectNavItem(item) } case .titleOptimizer: if let item = viewModel.createItems.first(where: { $0.iconKind == .titleOptimizer }) { titleOptimizerViewModel.restore(from: entry) viewModel.selectNavItem(item) } case .commentWriter: if let item = viewModel.createItems.first(where: { $0.iconKind == .commentWriter }) { commentWriterViewModel.restore(from: entry) viewModel.selectNavItem(item) } } } } private struct ToolPlaceholderView: View { let item: SidebarNavItem var body: some View { VStack(spacing: 12) { ModernSidebarIconView(kind: item.iconKind, size: 56) Text(item.title) .font(.system(size: 20, weight: .semibold)) .foregroundStyle(AppTheme.textPrimary) Text(item.subtitle) .font(.system(size: 13)) .foregroundStyle(AppTheme.textSecondary) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(AppTheme.background) } } #Preview { FrontPageView(session: AppSessionState()) .environmentObject(SubscriptionManager()) .environmentObject(AppRatingManager()) .environment(AppearanceManager.shared) .frame(width: AppWindow.width, height: AppWindow.height) }