SidebarView.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import SwiftUI
  2. struct SidebarView: View {
  3. @Environment(\.requirePremiumAccess) private var requirePremiumAccess
  4. @EnvironmentObject private var subscriptions: SubscriptionManager
  5. @ObservedObject private var paywallConfigService = PaywallConfigService.shared
  6. @Bindable var viewModel: FrontPageViewModel
  7. var isPremium: Bool
  8. private var sidebarConfig: PaywallConfig.Sidebar {
  9. paywallConfigService.config.sidebar
  10. }
  11. var body: some View {
  12. VStack(spacing: 0) {
  13. header
  14. navigationSections
  15. Spacer(minLength: 0)
  16. upgradeCard
  17. }
  18. .frame(width: AppTheme.sidebarWidth)
  19. .frame(maxHeight: .infinity, alignment: .top)
  20. .background(AppTheme.sidebarBackground)
  21. }
  22. private var header: some View {
  23. HStack(spacing: AppTheme.sidebarRowSpacing) {
  24. Image("AppLogo")
  25. .resizable()
  26. .interpolation(.high)
  27. .scaledToFit()
  28. .frame(width: AppTheme.iconContainerSize, height: AppTheme.iconContainerSize)
  29. VStack(alignment: .leading, spacing: 2) {
  30. Text(AppLinks.appName)
  31. .font(.system(size: 15, weight: .bold))
  32. .foregroundStyle(AppTheme.textPrimary)
  33. Text("AI Assistant for Reddit")
  34. .font(.system(size: 10))
  35. .foregroundStyle(AppTheme.textSecondary)
  36. }
  37. Spacer(minLength: 0)
  38. }
  39. .padding(.horizontal, AppTheme.sidebarContentInset + AppTheme.sidebarRowPaddingH)
  40. .padding(.top, 20)
  41. .padding(.bottom, 16)
  42. }
  43. private var navigationSections: some View {
  44. ScrollView {
  45. VStack(alignment: .leading, spacing: AppTheme.sidebarSectionSpacing) {
  46. sidebarSection(title: SidebarSection.quickAccess.rawValue) {
  47. SidebarQuickAccessItemView(isSelected: viewModel.isRedditActive) {
  48. guard requirePremiumAccess() else { return }
  49. viewModel.openReddit()
  50. }
  51. }
  52. sidebarSection(title: SidebarSection.create.rawValue) {
  53. ForEach(viewModel.createItems) { item in
  54. SidebarNavItemView(
  55. item: item,
  56. isSelected: !viewModel.isSettingsActive && !viewModel.isHistoryActive && !viewModel.isRedditActive && viewModel.selectedNavItem == item
  57. ) {
  58. viewModel.selectNavItem(item)
  59. }
  60. }
  61. }
  62. sidebarSection(title: SidebarSection.history.rawValue) {
  63. SidebarNavItemView(
  64. item: FrontPageViewModel.historyItem,
  65. isSelected: viewModel.isHistoryActive
  66. ) {
  67. viewModel.openHistory()
  68. }
  69. }
  70. sidebarSection(title: SidebarSection.settings.rawValue) {
  71. SidebarNavItemView(
  72. item: FrontPageViewModel.settingsItem,
  73. isSelected: viewModel.isSettingsActive
  74. ) {
  75. viewModel.openSettings()
  76. }
  77. }
  78. }
  79. .padding(.horizontal, AppTheme.sidebarContentInset)
  80. .padding(.top, 4)
  81. }
  82. }
  83. private func sidebarSection<Content: View>(
  84. title: String,
  85. @ViewBuilder content: () -> Content
  86. ) -> some View {
  87. VStack(alignment: .leading, spacing: AppTheme.sidebarItemSpacing) {
  88. SidebarSectionHeader(title: title)
  89. content()
  90. }
  91. }
  92. private var upgradeCard: some View {
  93. VStack(spacing: 0) {
  94. Rectangle()
  95. .fill(AppTheme.border)
  96. .frame(height: 1)
  97. VStack(alignment: .center, spacing: 8) {
  98. VStack(spacing: 4) {
  99. Image(systemName: "crown.fill")
  100. .font(.system(size: 14))
  101. .foregroundStyle(AppTheme.accentYellow)
  102. Text(isPremium ? sidebarConfig.proTitle : sidebarConfig.unlockTitle)
  103. .font(.system(size: 12, weight: .semibold))
  104. .foregroundStyle(AppTheme.textPrimary)
  105. }
  106. .frame(maxWidth: .infinity, alignment: .center)
  107. Text(isPremium ? sidebarConfig.proDescription : sidebarConfig.unlockDescription)
  108. .font(.system(size: 10))
  109. .foregroundStyle(AppTheme.textSecondary)
  110. .multilineTextAlignment(.center)
  111. .frame(maxWidth: .infinity, alignment: .center)
  112. .fixedSize(horizontal: false, vertical: true)
  113. .lineSpacing(2)
  114. Button {
  115. if isPremium {
  116. subscriptions.openSubscriptionManagement()
  117. } else {
  118. viewModel.showPaywall()
  119. }
  120. } label: {
  121. Text(premiumActionTitle)
  122. .font(.system(size: 11, weight: .semibold))
  123. .foregroundStyle(.white)
  124. .frame(maxWidth: .infinity)
  125. .padding(.vertical, 7.5)
  126. .background(AppTheme.accentPurple)
  127. .clipShape(RoundedRectangle(cornerRadius: 8))
  128. .contentShape(RoundedRectangle(cornerRadius: 8))
  129. }
  130. .buttonStyle(AppPrimaryButtonStyle())
  131. }
  132. .frame(maxWidth: .infinity, minHeight: 105)
  133. .padding(.horizontal, 12)
  134. .padding(.vertical, 12)
  135. .background(
  136. RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
  137. .fill(AppTheme.cardBackground)
  138. .overlay(
  139. RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
  140. .stroke(AppTheme.accentPurple.opacity(0.25), lineWidth: 1)
  141. )
  142. )
  143. .padding(.horizontal, AppTheme.sidebarContentInset + 8)
  144. .padding(.top, 12)
  145. .padding(.bottom, 20)
  146. }
  147. }
  148. private var premiumActionTitle: String {
  149. isPremium ? sidebarConfig.manageSubscriptionAction : sidebarConfig.upgradeAction
  150. }
  151. }