import SwiftUI import AppKit struct LicenseEntryView: View { @ObservedObject private var licenseManager = LicenseManager.shared @Environment(\.presentationMode) var presentationMode @State private var licenseKey = "" @State private var userName = "" @State private var isVerifying = false @State private var errorMessage = "" @State private var showError = false // Add closure for window dismissal var onDismiss: (() -> Void)? init(onDismiss: (() -> Void)? = nil) { self.onDismiss = onDismiss } var body: some View { VStack(spacing: 0) { ScrollView { VStack(spacing: 24) { // Add minimal top padding since traffic lights are hidden Spacer() .frame(height: 20) VStack(spacing: 24) { // Header Section - similar to SettingsTabView VStack(spacing: 16) { // Replace key icon with ShotScreen icon at native 200x200 resolution Group { if let shotScreenIcon = NSImage(named: "ShotScreenIcon_200x200") { Image(nsImage: shotScreenIcon) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200) .cornerRadius(20) } else if let bundle = Bundle.main.url(forResource: "images/ShotScreenIcon_200x200", withExtension: "png"), let nsImage = NSImage(contentsOf: bundle) { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 200, height: 200) .cornerRadius(20) } else { // Fallback to system icon if ShotScreenIcon_200x200 not found Image(systemName: "key.fill") .font(.system(size: 48)) .foregroundColor(.blue) } } VStack(spacing: 8) { Text(headerTitle) .font(.largeTitle) .fontWeight(.bold) .foregroundColor(.primary) .multilineTextAlignment(.center) Text(headerSubtitle) .font(.subheadline) .foregroundColor(.secondary) .multilineTextAlignment(.center) } } .padding(.top, 10) .padding(.bottom, 10) // Trial Status Section switch licenseManager.licenseStatus { case .trial(let daysLeft): VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("Trial Status") .font(.title2) .fontWeight(.semibold) .foregroundColor(.primary) Divider() } HStack { Image(systemName: "clock.fill") .foregroundColor(.orange) Text("\(daysLeft) days remaining in your free trial") .foregroundColor(.secondary) } Text("Already purchased? Enter your license key below to activate the full version.") .font(.caption) .foregroundColor(.secondary) } .padding(20) .background(Color.primary.opacity(0.03)) .cornerRadius(12) case .expired: VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("Trial Expired") .font(.title2) .fontWeight(.semibold) .foregroundColor(.red) Divider() } HStack { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) Text("Your 7-day trial has expired") .foregroundColor(.secondary) } Text("Purchase a license or enter your license key to continue using ShotScreen.") .font(.caption) .foregroundColor(.secondary) } .padding(20) .background(Color.red.opacity(0.05)) .cornerRadius(12) default: EmptyView() } // License Entry Form Section VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("License Information") .font(.title2) .fontWeight(.semibold) .foregroundColor(.primary) Divider() } VStack(spacing: 16) { VStack(alignment: .leading, spacing: 8) { Text("Your Name") .font(.headline) .foregroundColor(.primary) TextField("Enter your full name", text: $userName) .textFieldStyle(RoundedBorderTextFieldStyle()) .disabled(isVerifying) } VStack(alignment: .leading, spacing: 8) { Text("License Key") .font(.headline) .foregroundColor(.primary) TextField("XXXX-XXXX-XXXX-XXXX", text: $licenseKey) .textFieldStyle(RoundedBorderTextFieldStyle()) .font(.system(.body, design: .monospaced)) .disabled(isVerifying) } if showError { HStack { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(.red) Text(errorMessage) .foregroundColor(.red) .font(.caption) } .padding(.horizontal, 12) .padding(.vertical, 8) .background(Color.red.opacity(0.1)) .cornerRadius(8) } } } .padding(20) .background(Color.primary.opacity(0.03)) .cornerRadius(12) Spacer() } .padding() } } Divider() // Bottom Button Section - matching SettingsTabView style HStack { Button(action: activateLicense) { HStack { if isVerifying { ProgressView() .scaleEffect(0.7) } Text(isVerifying ? "Verifying..." : "Activate License") } } .buttonStyle(.borderedProminent) .disabled(!canActivate || isVerifying) .keyboardShortcut(.defaultAction) // Return key = Activate Spacer() Button("Buy License") { openPurchaseURL() } .buttonStyle(.bordered) Button("Cancel") { dismissWindow() } .buttonStyle(.bordered) } .padding() } .frame(minWidth: 600, idealWidth: 650, minHeight: 700, idealHeight: 750) .background(Color.clear) } private var canActivate: Bool { !licenseKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !userName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } private var headerTitle: String { switch licenseManager.licenseStatus { case .trial(_): return "Activate ShotScreen" case .expired: return "Unlock ShotScreen" default: return "Activate ShotScreen" } } private var headerSubtitle: String { switch licenseManager.licenseStatus { case .trial(_): return "Already have a license? Enter your license key below." case .expired: return "Your free trial has ended. Enter your license key to continue using ShotScreen." default: return "Enter your license key to unlock the full version" } } private func activateLicense() { guard canActivate else { return } isVerifying = true showError = false errorMessage = "" Task { let success = await licenseManager.enterLicenseKey( licenseKey.trimmingCharacters(in: .whitespacesAndNewlines), userName: userName.trimmingCharacters(in: .whitespacesAndNewlines) ) await MainActor.run { isVerifying = false if success { dismissWindow() } else { showError = true errorMessage = "Invalid license key or verification failed. Please check your license key and internet connection." } } } } private func openPurchaseURL() { // ShotScreen Gumroad license product if let url = URL(string: "https://roodenrijs.gumroad.com/l/uxexr") { NSWorkspace.shared.open(url) } } private func dismissWindow() { onDismiss?() presentationMode.wrappedValue.dismiss() } } // MARK: - License Entry Window class LicenseEntryWindow: NSWindow { private var visualEffectViewContainer: NSView? override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) { super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag) setupWindow() } convenience init() { self.init( contentRect: NSRect(x: 0, y: 0, width: 650, height: 750), styleMask: [.titled, .closable, .resizable, .fullSizeContentView], backing: .buffered, defer: false ) } private func setupWindow() { title = "ShotScreen License" isReleasedWhenClosed = false center() // Ensure the window appears in front and can be brought to front level = .normal collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] // Apply same blur style as SettingsWindow isOpaque = false backgroundColor = .clear titlebarAppearsTransparent = true titleVisibility = .hidden isMovableByWindowBackground = true // Hide traffic lights (red, yellow, green buttons) to prevent content overlap standardWindowButton(.closeButton)?.isHidden = true standardWindowButton(.miniaturizeButton)?.isHidden = true standardWindowButton(.zoomButton)?.isHidden = true // Create content view with dismissal callback let licenseView = LicenseEntryView(onDismiss: { [weak self] in self?.close() }) let hostingView = NSHostingView(rootView: licenseView) // Apply same visual effect as SettingsWindow let visualEffectView = NSVisualEffectView() visualEffectView.blendingMode = .behindWindow visualEffectView.material = .hudWindow // Same strong system blur visualEffectView.state = .active visualEffectView.alphaValue = 1.0 visualEffectView.autoresizingMask = [.width, .height] // Extra blur layer for enhanced effect let extraBlurView = NSVisualEffectView() extraBlurView.blendingMode = .behindWindow extraBlurView.material = .hudWindow extraBlurView.state = .active extraBlurView.alphaValue = 0.6 // Half transparent for optical blur enhancement extraBlurView.autoresizingMask = [.width, .height] let newRootContentView = NSView(frame: contentRect(forFrameRect: frame)) // Layer order: strongest blur at bottom, half blur above, SwiftUI content on top visualEffectView.frame = newRootContentView.bounds extraBlurView.frame = newRootContentView.bounds newRootContentView.addSubview(visualEffectView) // Layer 0 newRootContentView.addSubview(extraBlurView) // Layer 1 (enhancement) newRootContentView.addSubview(hostingView) // Layer 2 (UI) contentView = newRootContentView visualEffectViewContainer = newRootContentView hostingView.wantsLayer = true hostingView.layer?.backgroundColor = NSColor.clear.cgColor hostingView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ hostingView.topAnchor.constraint(equalTo: newRootContentView.topAnchor), hostingView.bottomAnchor.constraint(equalTo: newRootContentView.bottomAnchor), hostingView.leadingAnchor.constraint(equalTo: newRootContentView.leadingAnchor), hostingView.trailingAnchor.constraint(equalTo: newRootContentView.trailingAnchor) ]) // Force a layout pass after setting up the hierarchy contentView?.layoutSubtreeIfNeeded() // Window appearance delegate = self // Better window management - bring to front orderFrontRegardless() makeKeyAndOrderFront(nil) } deinit { print("🗑️ LicenseEntryWindow deinitialized safely") } } // MARK: - Window Delegate extension LicenseEntryWindow: NSWindowDelegate { func windowShouldClose(_ sender: NSWindow) -> Bool { // Allow closing return true } func windowWillClose(_ notification: Notification) { // Clean up when window is about to close contentView = nil delegate = nil print("🪟 License window closing gracefully") } } // MARK: - Trial Expired View struct TrialExpiredView: View { @ObservedObject private var licenseManager = LicenseManager.shared var body: some View { VStack(spacing: 24) { // Replace with ShotScreen icon using 200x200 source at smaller display size Group { if let shotScreenIcon = NSImage(named: "ShotScreenIcon_200x200") { Image(nsImage: shotScreenIcon) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 100, height: 100) .cornerRadius(16) } else if let bundle = Bundle.main.url(forResource: "images/ShotScreenIcon_200x200", withExtension: "png"), let nsImage = NSImage(contentsOf: bundle) { Image(nsImage: nsImage) .resizable() .aspectRatio(contentMode: .fit) .frame(width: 100, height: 100) .cornerRadius(16) } else { // Fallback icon Image(systemName: "exclamationmark.triangle.fill") .font(.system(size: 64)) .foregroundColor(.orange) } } // Title Text("Trial Expired") .font(.largeTitle) .fontWeight(.bold) // Message Text("Your 7-day trial of ShotScreen has expired.\nPlease purchase a license to continue using the app.") .font(.body) .multilineTextAlignment(.center) .foregroundColor(.secondary) // Buttons - matching new style VStack(spacing: 12) { Button("Purchase License") { openPurchaseURL() } .buttonStyle(.borderedProminent) Button("Enter License Key") { licenseManager.showLicenseEntryDialog() } .buttonStyle(.bordered) Button("Quit ShotScreen") { NSApplication.shared.terminate(nil) } .buttonStyle(.bordered) .foregroundColor(.red) } } .padding(40) .frame(width: 400, height: 350) .background(Color(NSColor.windowBackgroundColor)) } private func openPurchaseURL() { // ShotScreen Gumroad license product if let url = URL(string: "https://roodenrijs.gumroad.com/l/uxexr") { NSWorkspace.shared.open(url) } } } #Preview { LicenseEntryView() }