FrontPageView.swift 6.5 KB

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