From b09443bc09a64bfe22f7d37608677246ef106688 Mon Sep 17 00:00:00 2001 From: Beingpax Date: Thu, 5 Jun 2025 14:51:41 +0545 Subject: [PATCH] Improved Onboarding Window+ License/buy buttons --- .../Views/Components/TrialMessageView.swift | 15 ++++++-- VoiceInk/Views/ContentView.swift | 3 +- VoiceInk/Views/MetricsView.swift | 20 +++++++++-- .../Views/Recorder/MiniWindowManager.swift | 2 +- .../Views/Recorder/NotchWindowManager.swift | 2 +- VoiceInk/VoiceInk.swift | 11 ++++-- VoiceInk/WindowManager.swift | 35 +++++++++++-------- 7 files changed, 62 insertions(+), 26 deletions(-) diff --git a/VoiceInk/Views/Components/TrialMessageView.swift b/VoiceInk/Views/Components/TrialMessageView.swift index 3ead555..8e55484 100644 --- a/VoiceInk/Views/Components/TrialMessageView.swift +++ b/VoiceInk/Views/Components/TrialMessageView.swift @@ -3,6 +3,7 @@ import SwiftUI struct TrialMessageView: View { let message: String let type: MessageType + var onAddLicenseKey: (() -> Void)? = nil enum MessageType { case warning @@ -26,14 +27,22 @@ struct TrialMessageView: View { Spacer() - if type == .expired || type == .warning { + HStack(spacing: 12) { + Button(action: { + onAddLicenseKey?() + }) { + Text("Enter License") + .font(.system(size: 13, weight: .medium)) + } + .buttonStyle(.bordered) + Button(action: { if let url = URL(string: "https://tryvoiceink.com/buy") { NSWorkspace.shared.open(url) } }) { - Text(type == .expired ? "Upgrade Now" : "Upgrade") - .font(.headline) + Text("Buy License") + .font(.system(size: 13, weight: .medium)) } .buttonStyle(.borderedProminent) } diff --git a/VoiceInk/Views/ContentView.swift b/VoiceInk/Views/ContentView.swift index fdf67f5..3387e47 100644 --- a/VoiceInk/Views/ContentView.swift +++ b/VoiceInk/Views/ContentView.swift @@ -190,8 +190,7 @@ struct ContentView: View { .navigationTitle("") } .navigationSplitViewStyle(.balanced) - .frame(minWidth: 1100, minHeight: 750) - .background(Color(.controlBackgroundColor)) + .frame(minWidth: 940, minHeight: 730) .onAppear { hasLoadedData = true } diff --git a/VoiceInk/Views/MetricsView.swift b/VoiceInk/Views/MetricsView.swift index 93315be..c162f04 100644 --- a/VoiceInk/Views/MetricsView.swift +++ b/VoiceInk/Views/MetricsView.swift @@ -22,13 +22,29 @@ struct MetricsView: View { if case .trial(let daysRemaining) = licenseViewModel.licenseState { TrialMessageView( message: "You have \(daysRemaining) days left in your trial", - type: daysRemaining <= 2 ? .warning : .info + type: daysRemaining <= 2 ? .warning : .info, + onAddLicenseKey: { + // Post notification to navigate to VoiceInk Pro tab + NotificationCenter.default.post( + name: .navigateToDestination, + object: nil, + userInfo: ["destination": "VoiceInk Pro"] + ) + } ) .padding() } else if case .trialExpired = licenseViewModel.licenseState { TrialMessageView( message: "Your trial has expired. Upgrade to continue using VoiceInk", - type: .expired + type: .expired, + onAddLicenseKey: { + // Also allow navigation from expired state + NotificationCenter.default.post( + name: .navigateToDestination, + object: nil, + userInfo: ["destination": "VoiceInk Pro"] + ) + } ) .padding() } diff --git a/VoiceInk/Views/Recorder/MiniWindowManager.swift b/VoiceInk/Views/Recorder/MiniWindowManager.swift index 48ebd2a..5045457 100644 --- a/VoiceInk/Views/Recorder/MiniWindowManager.swift +++ b/VoiceInk/Views/Recorder/MiniWindowManager.swift @@ -80,4 +80,4 @@ class MiniWindowManager: ObservableObject { show() } } -} \ No newline at end of file +} diff --git a/VoiceInk/Views/Recorder/NotchWindowManager.swift b/VoiceInk/Views/Recorder/NotchWindowManager.swift index c3ad2fc..cffd2c4 100644 --- a/VoiceInk/Views/Recorder/NotchWindowManager.swift +++ b/VoiceInk/Views/Recorder/NotchWindowManager.swift @@ -4,7 +4,7 @@ import AppKit class NotchWindowManager: ObservableObject { @Published var isVisible = false private var windowController: NSWindowController? - private var notchPanel: NotchRecorderPanel? + var notchPanel: NotchRecorderPanel? private let whisperState: WhisperState private let recorder: Recorder diff --git a/VoiceInk/VoiceInk.swift b/VoiceInk/VoiceInk.swift index 38e92ac..8028332 100644 --- a/VoiceInk/VoiceInk.swift +++ b/VoiceInk/VoiceInk.swift @@ -114,8 +114,15 @@ struct VoiceInkApp: App { .environmentObject(whisperState) .environmentObject(aiService) .environmentObject(enhancementService) - .frame(minWidth: 1200, minHeight: 800) - + .frame(minWidth: 880, minHeight: 780) + .cornerRadius(16) + .clipped() + .background(WindowAccessor { window in + // Ensure this is called only once or is idempotent + if window.title != "VoiceInk Onboarding" { // Prevent re-configuration + WindowManager.shared.configureOnboardingPanel(window) + } + }) } } .commands { diff --git a/VoiceInk/WindowManager.swift b/VoiceInk/WindowManager.swift index f6f54a6..2719b86 100644 --- a/VoiceInk/WindowManager.swift +++ b/VoiceInk/WindowManager.swift @@ -7,29 +7,38 @@ class WindowManager { private init() {} func configureWindow(_ window: NSWindow) { + window.styleMask = [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView] window.titlebarAppearsTransparent = true window.titleVisibility = .hidden - window.styleMask.insert(.fullSizeContentView) window.backgroundColor = .windowBackgroundColor window.isReleasedWhenClosed = false window.title = "VoiceInk" - - // Add additional window configuration for better state management window.collectionBehavior = [.fullScreenPrimary] - - // Ensure proper window level and ordering window.level = .normal + window.isOpaque = true + window.isMovableByWindowBackground = false + window.minSize = NSSize(width: 0, height: 0) + window.orderFrontRegardless() + } + + func configureOnboardingPanel(_ window: NSWindow) { + window.styleMask = [.borderless, .fullSizeContentView, .resizable] + window.isMovableByWindowBackground = true + window.level = .floating + window.titlebarAppearsTransparent = true + window.titleVisibility = .hidden + window.backgroundColor = .clear + window.isReleasedWhenClosed = false + window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle] + window.title = "VoiceInk Onboarding" + window.isOpaque = false + window.minSize = NSSize(width: 900, height: 780) window.orderFrontRegardless() } func createMainWindow(contentView: NSView) -> NSWindow { - // Use a standard size that fits well on most displays - let defaultSize = NSSize(width: 1200, height: 800) - - // Get the main screen frame to help with centering + let defaultSize = NSSize(width: 940, height: 780) let screenFrame = NSScreen.main?.visibleFrame ?? NSRect(x: 0, y: 0, width: 1200, height: 800) - - // Create window with centered position let xPosition = (screenFrame.width - defaultSize.width) / 2 + screenFrame.minX let yPosition = (screenFrame.height - defaultSize.height) / 2 + screenFrame.minY @@ -43,7 +52,6 @@ class WindowManager { configureWindow(window) window.contentView = contentView - // Set up window delegate to handle window state changes let delegate = WindowStateDelegate() window.delegate = delegate @@ -51,16 +59,13 @@ class WindowManager { } } -// Add window delegate to handle window state changes class WindowStateDelegate: NSObject, NSWindowDelegate { func windowWillClose(_ notification: Notification) { guard let window = notification.object as? NSWindow else { return } - // Ensure window is properly hidden when closed window.orderOut(nil) } func windowDidBecomeKey(_ notification: Notification) { - // Ensure window is properly activated guard let _ = notification.object as? NSWindow else { return } NSApp.activate(ignoringOtherApps: true) }