FrontPageView.swift 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import SwiftUI
  2. struct FrontPageView: View {
  3. @EnvironmentObject private var subscriptions: SubscriptionManager
  4. @EnvironmentObject private var appRating: AppRatingManager
  5. @Environment(AppearanceManager.self) private var appearanceManager
  6. @State private var viewModel = FrontPageViewModel()
  7. @State private var paywallViewModel = PaywallViewModel()
  8. @State private var postGeneratorViewModel = PostGeneratorViewModel()
  9. @State private var titleOptimizerViewModel = TitleOptimizerViewModel()
  10. @State private var commentWriterViewModel = CommentWriterViewModel()
  11. @State private var settingsViewModel = SettingsViewModel()
  12. var body: some View {
  13. let _ = appearanceManager.isDarkMode
  14. ZStack {
  15. HStack(spacing: 0) {
  16. SidebarView(
  17. viewModel: viewModel,
  18. isPremium: subscriptions.hasPremiumAccess
  19. )
  20. mainContent
  21. .frame(maxWidth: .infinity, maxHeight: .infinity)
  22. .environment(\.requirePremiumAccess, {
  23. if subscriptions.hasPremiumAccess { return true }
  24. viewModel.showPaywall()
  25. return false
  26. })
  27. }
  28. if viewModel.isPaywallPresented {
  29. PaywallView(viewModel: paywallViewModel) {
  30. viewModel.dismissPaywall()
  31. }
  32. .frame(maxWidth: .infinity, maxHeight: .infinity)
  33. .ignoresSafeArea()
  34. .zIndex(1)
  35. .transition(.opacity)
  36. }
  37. }
  38. .animation(.easeInOut(duration: 0.2), value: viewModel.isPaywallPresented)
  39. .onChange(of: subscriptions.hasPremiumAccess) { hasPremium in
  40. if hasPremium {
  41. viewModel.dismissPaywall()
  42. }
  43. appRating.updatePremiumStatus(hasPremium)
  44. }
  45. .onAppear {
  46. appRating.updatePremiumStatus(subscriptions.hasPremiumAccess)
  47. }
  48. .frame(minWidth: AppWindow.minWidth, minHeight: AppWindow.minHeight)
  49. .background(AppTheme.background)
  50. .alert("Enjoying \(AppLinks.appName)?", isPresented: Binding(
  51. get: { appRating.showRatingPrompt },
  52. set: { isPresented in
  53. if !isPresented, appRating.showRatingPrompt {
  54. appRating.userDeclinedRating()
  55. }
  56. }
  57. )) {
  58. Button("Rate App") {
  59. appRating.userAcceptedRating()
  60. }
  61. Button("Not Now", role: .cancel) {
  62. appRating.userDeclinedRating()
  63. }
  64. } message: {
  65. Text("Would you like to rate \(AppLinks.appName) on the App Store? Your feedback helps us improve.")
  66. }
  67. .alert("Sign-in issue", isPresented: authErrorBinding) {
  68. Button("OK") {
  69. viewModel.clearAuthError()
  70. }
  71. } message: {
  72. Text(viewModel.authErrorMessage ?? "")
  73. }
  74. }
  75. private var authErrorBinding: Binding<Bool> {
  76. Binding(
  77. get: { viewModel.authErrorMessage != nil },
  78. set: { isPresented in
  79. if !isPresented {
  80. viewModel.clearAuthError()
  81. }
  82. }
  83. )
  84. }
  85. @ViewBuilder
  86. private var mainContent: some View {
  87. VStack(spacing: 0) {
  88. if viewModel.isRedditActive, viewModel.redditCanGoBack {
  89. RedditWebBackBar(
  90. canGoBack: true,
  91. onBack: { viewModel.goBackInReddit() }
  92. )
  93. }
  94. ZStack {
  95. RedditWebView(
  96. url: viewModel.redditLoadURL,
  97. reloadTrigger: viewModel.redditReloadTrigger,
  98. isDarkMode: appearanceManager.isDarkMode,
  99. goBackTrigger: viewModel.redditGoBackTrigger,
  100. onCanGoBackChange: { viewModel.updateRedditCanGoBack($0) },
  101. onAuthError: { viewModel.showAuthError($0) }
  102. )
  103. .id("reddit-webview")
  104. .frame(maxWidth: .infinity, maxHeight: .infinity)
  105. .allowsHitTesting(viewModel.isRedditActive)
  106. .accessibilityHidden(!viewModel.isRedditActive)
  107. if viewModel.isSettingsActive {
  108. SettingsView(viewModel: settingsViewModel)
  109. } else if !viewModel.isRedditActive, let item = viewModel.selectedNavItem {
  110. toolContent(for: item)
  111. }
  112. }
  113. }
  114. .background(AppTheme.background)
  115. }
  116. @ViewBuilder
  117. private func toolContent(for item: SidebarNavItem) -> some View {
  118. switch item.iconKind {
  119. case .postGenerator:
  120. PostGeneratorView(viewModel: postGeneratorViewModel)
  121. case .titleOptimizer:
  122. TitleOptimizerView(viewModel: titleOptimizerViewModel)
  123. case .commentWriter:
  124. CommentWriterView(viewModel: commentWriterViewModel)
  125. default:
  126. ToolPlaceholderView(item: item)
  127. }
  128. }
  129. }
  130. private struct ToolPlaceholderView: View {
  131. let item: SidebarNavItem
  132. var body: some View {
  133. VStack(spacing: 12) {
  134. ModernSidebarIconView(kind: item.iconKind, size: 56)
  135. Text(item.title)
  136. .font(.system(size: 20, weight: .semibold))
  137. .foregroundStyle(AppTheme.textPrimary)
  138. Text(item.subtitle)
  139. .font(.system(size: 13))
  140. .foregroundStyle(AppTheme.textSecondary)
  141. }
  142. .frame(maxWidth: .infinity, maxHeight: .infinity)
  143. .background(AppTheme.background)
  144. }
  145. }
  146. #Preview {
  147. FrontPageView()
  148. .environmentObject(SubscriptionManager())
  149. .environmentObject(AppRatingManager())
  150. .environment(AppearanceManager.shared)
  151. .frame(width: AppWindow.width, height: AppWindow.height)
  152. }