| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- 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 subscriptions.hasPremiumAccess
- }
- 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<Bool> {
- 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)
- }
|