// // 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() case .zen: ZenMiniPreview() case .editorial: EditorialMiniPreview() case .mixtape: MixtapeMiniPreview() case .heartfelt: HeartfeltMiniPreview() case .luxe: LuxeMiniPreview() case .forecast: ForecastMiniPreview() case .playful: PlayfulMiniPreview() case .journal: JournalMiniPreview() } } 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)] case .zen: return [Color(red: 0.6, green: 0.7, blue: 0.6), Color(red: 0.5, green: 0.6, blue: 0.55)] case .editorial: return [Color(red: 0.15, green: 0.15, blue: 0.15), Color(red: 0.3, green: 0.3, blue: 0.3)] case .mixtape: return [Color(red: 0.95, green: 0.45, blue: 0.35), Color(red: 0.95, green: 0.65, blue: 0.25)] case .heartfelt: return [Color(red: 0.9, green: 0.45, blue: 0.55), Color(red: 0.95, green: 0.6, blue: 0.65)] case .luxe: return [Color(red: 0.75, green: 0.6, blue: 0.35), Color(red: 0.55, green: 0.45, blue: 0.25)] case .forecast: return [Color(red: 0.4, green: 0.65, blue: 0.85), Color(red: 0.3, green: 0.5, blue: 0.75)] case .playful: return [Color(red: 0.95, green: 0.55, blue: 0.35), Color(red: 0.95, green: 0.75, blue: 0.35)] case .journal: return [Color(red: 0.55, green: 0.45, blue: 0.35), Color(red: 0.4, green: 0.35, blue: 0.3)] } } } // 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" case .zen: return "circle" case .editorial: return "textformat" case .mixtape: return "opticaldisc.fill" case .heartfelt: return "heart.fill" case .luxe: return "diamond.fill" case .forecast: return "cloud.fill" case .playful: return "face.smiling.fill" case .journal: return "book.closed.fill" } } 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 ) case .zen: return LinearGradient( colors: [Color(red: 0.6, green: 0.7, blue: 0.6), Color(red: 0.5, green: 0.6, blue: 0.55)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .editorial: return LinearGradient( colors: [Color(red: 0.15, green: 0.15, blue: 0.15), Color(red: 0.3, green: 0.3, blue: 0.3)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .mixtape: return LinearGradient( colors: [Color(red: 0.95, green: 0.45, blue: 0.35), Color(red: 0.95, green: 0.65, blue: 0.25)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .heartfelt: return LinearGradient( colors: [Color(red: 0.9, green: 0.45, blue: 0.55), Color(red: 0.95, green: 0.6, blue: 0.65)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .luxe: return LinearGradient( colors: [Color(red: 0.75, green: 0.6, blue: 0.35), Color(red: 0.55, green: 0.45, blue: 0.25)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .forecast: return LinearGradient( colors: [Color(red: 0.4, green: 0.65, blue: 0.85), Color(red: 0.3, green: 0.5, blue: 0.75)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .playful: return LinearGradient( colors: [Color(red: 0.95, green: 0.55, blue: 0.35), Color(red: 0.95, green: 0.75, blue: 0.35)], startPoint: .topLeading, endPoint: .bottomTrailing ) case .journal: return LinearGradient( colors: [Color(red: 0.55, green: 0.45, blue: 0.35), Color(red: 0.4, green: 0.35, blue: 0.3)], 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 } } .onDisappear { animate = false } } 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 } } .onDisappear { bloom = false } } } 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 } } .onDisappear { pulse = false } } } 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 } } .onDisappear { breathe = false } } } struct ZenMiniPreview: View { @State private var breathe = false var body: some View { ZStack { // Warm paper background LinearGradient( colors: [ Color(red: 0.96, green: 0.94, blue: 0.90), Color(red: 0.92, green: 0.90, blue: 0.86) ], startPoint: .top, endPoint: .bottom ) // Content VStack(spacing: 16) { // Enso circle Circle() .stroke( Color(red: 0.3, green: 0.35, blue: 0.3), style: StrokeStyle(lineWidth: 3, lineCap: .round) ) .frame(width: breathe ? 55 : 50, height: breathe ? 55 : 50) .rotationEffect(.degrees(-30)) Text("Find Your\nInner Peace") .font(.system(size: 18, weight: .light, design: .serif)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.25, green: 0.25, blue: 0.2)) } } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { breathe = true } } .onDisappear { breathe = false } } } struct EditorialMiniPreview: View { var body: some View { ZStack { // Deep black background Color.black // Content VStack(spacing: 16) { // Simple geometric element Rectangle() .fill(Color.white) .frame(width: 40, height: 2) Text("THE ART\nOF FEELING") .font(.system(size: 16, weight: .bold, design: .serif)) .tracking(3) .multilineTextAlignment(.center) .foregroundColor(.white) Rectangle() .fill(Color.white) .frame(width: 40, height: 2) } } } } struct MixtapeMiniPreview: View { @State private var spin = false var body: some View { ZStack { // Warm gradient background LinearGradient( colors: [ Color(red: 0.95, green: 0.45, blue: 0.35), Color(red: 0.95, green: 0.65, blue: 0.25) ], startPoint: .topLeading, endPoint: .bottomTrailing ) // Content VStack(spacing: 12) { // Mini cassette ZStack { RoundedRectangle(cornerRadius: 4) .fill(Color.black.opacity(0.8)) .frame(width: 50, height: 32) HStack(spacing: 10) { Circle() .fill(Color.white.opacity(0.9)) .frame(width: 14, height: 14) .rotationEffect(.degrees(spin ? 360 : 0)) Circle() .fill(Color.white.opacity(0.9)) .frame(width: 14, height: 14) .rotationEffect(.degrees(spin ? 360 : 0)) } } Text("YOUR MOOD\nMIXTAPE") .font(.system(size: 14, weight: .black)) .multilineTextAlignment(.center) .foregroundColor(.white) } } .onAppear { withAnimation(.linear(duration: 3).repeatForever(autoreverses: false)) { spin = true } } .onDisappear { spin = false } } } struct HeartfeltMiniPreview: View { @State private var beat = false var body: some View { ZStack { // Soft pink gradient LinearGradient( colors: [ Color(red: 1.0, green: 0.95, blue: 0.95), Color(red: 0.98, green: 0.9, blue: 0.92) ], startPoint: .top, endPoint: .bottom ) // Content VStack(spacing: 12) { // Floating hearts HStack(spacing: -8) { ForEach(0..<3, id: \.self) { i in Image(systemName: "heart.fill") .font(.system(size: 20 - CGFloat(i * 4))) .foregroundColor(Color(red: 0.9, green: 0.45, blue: 0.55)) .scaleEffect(beat ? 1.1 : 0.95) } } Text("Feel With\nAll Your Heart") .font(.system(size: 17, weight: .medium, design: .serif)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.4, green: 0.25, blue: 0.3)) } } .onAppear { withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { beat = true } } .onDisappear { beat = false } } } struct LuxeMiniPreview: View { @State private var shimmer = false var body: some View { ZStack { // Deep rich background LinearGradient( colors: [ Color(red: 0.12, green: 0.1, blue: 0.08), Color(red: 0.08, green: 0.06, blue: 0.04) ], startPoint: .top, endPoint: .bottom ) // Content VStack(spacing: 14) { // Diamond icon Image(systemName: "diamond.fill") .font(.system(size: 36)) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.85, green: 0.7, blue: 0.45), Color(red: 0.65, green: 0.5, blue: 0.3) ], startPoint: shimmer ? .topLeading : .bottomTrailing, endPoint: shimmer ? .bottomTrailing : .topLeading ) ) Text("Elevate Your\nEmotional Life") .font(.system(size: 16, weight: .light, design: .serif)) .tracking(1) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.85, green: 0.8, blue: 0.7)) } } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { shimmer = true } } .onDisappear { shimmer = false } } } struct ForecastMiniPreview: View { @State private var drift = false var body: some View { ZStack { // Sky gradient LinearGradient( colors: [ Color(red: 0.55, green: 0.75, blue: 0.95), Color(red: 0.4, green: 0.6, blue: 0.85) ], startPoint: .top, endPoint: .bottom ) // Floating clouds HStack(spacing: 20) { Image(systemName: "cloud.fill") .font(.system(size: 28)) .foregroundColor(.white.opacity(0.8)) .offset(x: drift ? 5 : -5) Image(systemName: "sun.max.fill") .font(.system(size: 24)) .foregroundColor(Color(red: 1.0, green: 0.85, blue: 0.4)) } .offset(y: -30) // Content VStack(spacing: 8) { Text("Your Emotional\nForecast") .font(.system(size: 17, weight: .semibold, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(.white) } .offset(y: 30) } .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { drift = true } } .onDisappear { drift = false } } } struct PlayfulMiniPreview: View { @State private var bounce = false var body: some View { ZStack { // Warm playful gradient LinearGradient( colors: [ Color(red: 1.0, green: 0.98, blue: 0.94), Color(red: 0.98, green: 0.95, blue: 0.9) ], startPoint: .top, endPoint: .bottom ) // Bouncing emojis HStack(spacing: 8) { Text("😊") .font(.system(size: 28)) .offset(y: bounce ? -8 : 0) Text("🎉") .font(.system(size: 24)) .offset(y: bounce ? 0 : -8) Text("✨") .font(.system(size: 20)) .offset(y: bounce ? -8 : 0) } .offset(y: -30) // Content VStack(spacing: 8) { Text("Make Tracking\nFun Again!") .font(.system(size: 17, weight: .bold, design: .rounded)) .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.3, green: 0.25, blue: 0.2)) } .offset(y: 35) } .onAppear { withAnimation(.easeInOut(duration: 0.6).repeatForever(autoreverses: true)) { bounce = true } } .onDisappear { bounce = false } } } struct JournalMiniPreview: View { var body: some View { ZStack { // Paper texture background LinearGradient( colors: [ Color(red: 0.95, green: 0.92, blue: 0.88), Color(red: 0.92, green: 0.88, blue: 0.82) ], startPoint: .top, endPoint: .bottom ) // Horizontal lines like notebook paper VStack(spacing: 18) { ForEach(0..<6, id: \.self) { _ in Rectangle() .fill(Color(red: 0.7, green: 0.65, blue: 0.6).opacity(0.3)) .frame(height: 1) } } .padding(.horizontal, 30) // Content VStack(spacing: 12) { Image(systemName: "book.closed.fill") .font(.system(size: 32)) .foregroundColor(Color(red: 0.5, green: 0.4, blue: 0.35)) Text("Write Your\nEmotional Story") .font(.system(size: 16, weight: .medium, design: .serif)) .italic() .multilineTextAlignment(.center) .foregroundColor(Color(red: 0.35, green: 0.3, blue: 0.25)) } } } } #Preview { NavigationStack { PaywallPreviewSettingsView() .environmentObject(IAPManager()) } } #endif