// // PaywallPreviewSettingsView.swift // Feels // // Debug view for previewing and switching paywall styles. // import SwiftUI #if DEBUG struct PaywallPreviewSettingsView: View { @Environment(\.dismiss) private var dismiss @State private var selectedStyle: PaywallStyle = .celestial @State private var showFullPreview = false @EnvironmentObject var iapManager: IAPManager var body: some View { ScrollView { VStack(spacing: 24) { headerSection stylePicker previewCard fullPreviewButton } .padding() } .background(Color(.systemGroupedBackground)) .navigationTitle("Paywall Styles") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } } } .sheet(isPresented: $showFullPreview) { FeelsSubscriptionStoreView(style: selectedStyle) .environmentObject(iapManager) } } private var headerSection: some View { VStack(spacing: 8) { Image(systemName: "paintpalette.fill") .font(.system(size: 40)) .foregroundStyle( LinearGradient( colors: [.purple, .pink, .orange], startPoint: .topLeading, endPoint: .bottomTrailing ) ) Text("Paywall Theme Lab") .font(.title2.bold()) Text("Preview and test different subscription paywall designs") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } .padding(.vertical) } private var stylePicker: some View { VStack(alignment: .leading, spacing: 12) { Text("Select Style") .font(.headline) ForEach(PaywallStyle.allCases, id: \.self) { style in StyleOptionRow( style: style, isSelected: selectedStyle == style, onTap: { selectedStyle = style } ) } } } private var previewCard: some View { VStack(spacing: 0) { // Mini preview header HStack { Text("Preview") .font(.caption.weight(.semibold)) .foregroundStyle(.secondary) Spacer() Text(selectedStyle.displayName) .font(.caption.weight(.medium)) .foregroundStyle(.secondary) } .padding(.horizontal, 16) .padding(.vertical, 10) .background(Color(.secondarySystemGroupedBackground)) // Mini preview content miniPreview .frame(height: 280) .clipped() } .clipShape(RoundedRectangle(cornerRadius: 16)) .overlay( RoundedRectangle(cornerRadius: 16) .stroke(Color(.separator), lineWidth: 0.5) ) } @ViewBuilder private var miniPreview: some View { switch selectedStyle { case .celestial: CelestialMiniPreview() case .garden: GardenMiniPreview() case .neon: NeonMiniPreview() case .minimal: MinimalMiniPreview() } } private var fullPreviewButton: some View { Button { showFullPreview = true } label: { HStack { Image(systemName: "arrow.up.left.and.arrow.down.right") Text("View Full Paywall") .fontWeight(.semibold) } .frame(maxWidth: .infinity) .padding() .background( LinearGradient( colors: gradientColors, startPoint: .leading, endPoint: .trailing ) ) .foregroundColor(.white) .clipShape(RoundedRectangle(cornerRadius: 14)) } } private var gradientColors: [Color] { switch selectedStyle { case .celestial: return [Color(red: 1.0, green: 0.4, blue: 0.5), Color(red: 0.6, green: 0.4, blue: 0.9)] case .garden: return [Color(red: 0.4, green: 0.75, blue: 0.45), Color(red: 0.3, green: 0.6, blue: 0.4)] case .neon: return [Color(red: 0.0, green: 0.9, blue: 0.7), Color(red: 0.9, green: 0.0, blue: 0.7)] case .minimal: return [Color(red: 0.85, green: 0.6, blue: 0.5), Color(red: 0.7, green: 0.5, blue: 0.45)] } } } // MARK: - Style Option Row struct StyleOptionRow: View { let style: PaywallStyle let isSelected: Bool let onTap: () -> Void var body: some View { Button(action: onTap) { HStack(spacing: 14) { // Style icon ZStack { Circle() .fill(iconGradient) .frame(width: 44, height: 44) Image(systemName: iconName) .font(.system(size: 18, weight: .semibold)) .foregroundColor(.white) } // Text VStack(alignment: .leading, spacing: 2) { Text(style.displayName) .font(.body.weight(.medium)) .foregroundColor(.primary) Text(style.description) .font(.caption) .foregroundColor(.secondary) } Spacer() // Selection indicator if isSelected { Image(systemName: "checkmark.circle.fill") .font(.title3) .foregroundColor(.accentColor) } } .padding(12) .background(Color(.secondarySystemGroupedBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(isSelected ? Color.accentColor : Color.clear, lineWidth: 2) ) } .buttonStyle(.plain) } private var iconName: String { switch style { case .celestial: return "sparkles" case .garden: return "leaf.fill" case .neon: return "bolt.fill" case .minimal: return "circle.grid.2x2" } } private var iconGradient: LinearGradient { switch style { case .celestial: return LinearGradient( colors: [Color(red: 1.0, green: 0.4, blue: 0.5), Color(red: 0.6, green: 0.4, blue: 0.9)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .garden: return LinearGradient( colors: [Color(red: 0.4, green: 0.75, blue: 0.45), Color(red: 0.3, green: 0.6, blue: 0.4)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .neon: return LinearGradient( colors: [Color(red: 0.0, green: 0.9, blue: 0.7), Color(red: 0.9, green: 0.0, blue: 0.7)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .minimal: return LinearGradient( colors: [Color(red: 0.85, green: 0.6, blue: 0.5), Color(red: 0.7, green: 0.5, blue: 0.45)], startPoint: .topLeading, endPoint: .bottomTrailing ) } } } // MARK: - Mini Previews struct CelestialMiniPreview: View { @State private var animate = false var body: some View { ZStack { // Background LinearGradient( colors: [ Color(red: 0.05, green: 0.05, blue: 0.12), Color(red: 0.08, green: 0.06, blue: 0.15) ], startPoint: .top, endPoint: .bottom ) // Glow Circle() .fill(Color(red: 1.0, green: 0.4, blue: 0.5).opacity(0.3)) .frame(width: 200, height: 200) .blur(radius: 60) .offset(y: animate ? -20 : 0) // Content VStack(spacing: 12) { // Mini orbs HStack(spacing: -10) { ForEach(0..<3, id: \.self) { i in Circle() .fill(orbColors[i]) .frame(width: 24, height: 24) .shadow(color: orbColors[i].opacity(0.5), radius: 8) } } Text("Understand\nYourself Deeper") .font(.system(size: 18, weight: .bold, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(.white) } } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { animate = true } } } private var orbColors: [Color] { [ Color(red: 1.0, green: 0.8, blue: 0.3), Color(red: 1.0, green: 0.5, blue: 0.5), Color(red: 0.6, green: 0.5, blue: 0.9) ] } } struct GardenMiniPreview: View { @State private var bloom = false var body: some View { ZStack { // Background LinearGradient( colors: [ Color(red: 0.05, green: 0.12, blue: 0.08), Color(red: 0.08, green: 0.18, blue: 0.1) ], startPoint: .top, endPoint: .bottom ) // Glow Circle() .fill(Color(red: 0.3, green: 0.7, blue: 0.4).opacity(0.25)) .frame(width: 200, height: 200) .blur(radius: 60) // Content VStack(spacing: 12) { // Mini flower ZStack { ForEach(0..<6, id: \.self) { i in Ellipse() .fill(Color(red: 1.0, green: 0.6, blue: 0.7)) .frame(width: 14, height: bloom ? 28 : 20) .offset(y: bloom ? -22 : -16) .rotationEffect(.degrees(Double(i) * 60)) } Circle() .fill(Color(red: 1.0, green: 0.9, blue: 0.6)) .frame(width: 20, height: 20) } Text("Watch Yourself\nBloom") .font(.system(size: 18, weight: .bold, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(.white) } } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { bloom = true } } } } struct NeonMiniPreview: View { @State private var pulse = false var body: some View { ZStack { // Background Color(red: 0.02, green: 0.02, blue: 0.05) // Grid Canvas { context, size in let spacing: CGFloat = 20 for y in stride(from: 0, to: size.height, by: spacing) { var path = Path() path.move(to: CGPoint(x: 0, y: y)) path.addLine(to: CGPoint(x: size.width, y: y)) context.stroke(path, with: .color(Color.cyan.opacity(0.1)), lineWidth: 0.5) } } // Glows Circle() .fill(Color.cyan.opacity(pulse ? 0.3 : 0.15)) .frame(width: 150, height: 150) .blur(radius: 50) .offset(y: -40) Circle() .fill(Color.pink.opacity(pulse ? 0.2 : 0.1)) .frame(width: 120, height: 120) .blur(radius: 40) .offset(x: 30, y: 40) // Content VStack(spacing: 12) { // Neon ring Circle() .stroke( LinearGradient( colors: [.cyan, .pink], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 3 ) .frame(width: 50, height: 50) .shadow(color: .cyan.opacity(0.5), radius: pulse ? 15 : 8) Text("UNLOCK YOUR\nFULL SIGNAL") .font(.system(size: 14, weight: .black, design: .monospaced)) .multilineTextAlignment(.center) .foregroundStyle( LinearGradient(colors: [.cyan, .pink], startPoint: .leading, endPoint: .trailing) ) } } .onAppear { withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { pulse = true } } } } struct MinimalMiniPreview: View { @State private var breathe = false var body: some View { ZStack { // Background LinearGradient( colors: [ Color(red: 0.98, green: 0.96, blue: 0.92), Color(red: 0.95, green: 0.93, blue: 0.88) ], startPoint: .top, endPoint: .bottom ) // Content VStack(spacing: 16) { // Breathing circles ZStack { Circle() .stroke(Color(red: 0.8, green: 0.7, blue: 0.6).opacity(0.3), lineWidth: 1) .frame(width: breathe ? 60 : 50, height: breathe ? 60 : 50) Circle() .fill(Color(red: 0.95, green: 0.6, blue: 0.5).opacity(0.4)) .frame(width: 30, height: 30) .scaleEffect(breathe ? 1.1 : 0.95) } Text("Simply\nKnow Yourself") .font(.system(size: 18, weight: .light, design: .serif)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.2, green: 0.15, blue: 0.1)) } } .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { breathe = true } } } } #Preview { NavigationStack { PaywallPreviewSettingsView() .environmentObject(IAPManager()) } } #endif