Ver código fonte

Refactor app rating into a production-ready premium-only flow.

Replace post-purchase prompts with randomized review requests after successful prints, refresh Settings when premium unlocks, and open the App Store review page for manual ratings.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 4 semanas atrás
pai
commit
e8f1d4446f

+ 100 - 257
smart_printer/AppRatingManager.swift

@@ -6,90 +6,80 @@ final class AppRatingManager {
     static let shared = AppRatingManager()
 
     private enum Constants {
-        static let usageThresholdSeconds: TimeInterval = 15 * 60
-        static let usageCheckInterval: TimeInterval = 30
-        static let promptPresentationDelay: TimeInterval = 1.5
-    }
-
-    private enum PromptSource {
-        case firstPurchase
-        case usageMilestone
-
-        var title: String {
-            switch self {
-            case .firstPurchase:
-                "Enjoying Smart Printer?"
-            case .usageMilestone:
-                "Enjoying Smart Printer?"
-            }
-        }
-
-        var message: String {
-            switch self {
-            case .firstPurchase:
-                "Thanks for going Premium! Do you like the app?"
-            case .usageMilestone:
-                "You've been using Smart Printer for a while. Would you mind rating us on the App Store?"
-            }
-        }
+        static let minLaunches = 5
+        static let minDaysSinceInstall: TimeInterval = 3 * 24 * 60 * 60
+        static let printThresholdRange = 3...8
+        static let cooldownBetweenPrompts: TimeInterval = 120 * 24 * 60 * 60
+        static let automaticPromptProbability = 0.30
+        static let reviewPresentationDelay: TimeInterval = 2.0
     }
 
     private struct State {
-        var firstPurchasePromptPending = false
-        var firstPurchasePromptCompleted = false
-        var usageBasedPromptCompleted = false
-        var hasRequestedReview = false
-        var accumulatedActiveUsageSeconds: TimeInterval = 0
+        var launchCount = 0
+        var successfulPrintCount = 0
+        var firstLaunchDate: Date?
+        var lastAutomaticPromptDate: Date?
+        var randomPrintThreshold: Int?
 
         static func load() -> State {
             let defaults = UserDefaults.standard
             var state = State()
 
-            if defaults.object(forKey: Key.accumulatedActiveUsageSeconds) != nil {
-                state.accumulatedActiveUsageSeconds = defaults.double(forKey: Key.accumulatedActiveUsageSeconds)
-            }
-
-            state.firstPurchasePromptPending = defaults.bool(forKey: Key.firstPurchasePromptPending)
-            state.firstPurchasePromptCompleted = defaults.bool(forKey: Key.firstPurchasePromptCompleted)
-            state.usageBasedPromptCompleted = defaults.bool(forKey: Key.usageBasedPromptCompleted)
-            state.hasRequestedReview = defaults.bool(forKey: Key.hasRequestedReview)
+            state.launchCount = defaults.integer(forKey: Key.launchCount)
+            state.successfulPrintCount = defaults.integer(forKey: Key.successfulPrintCount)
+            state.randomPrintThreshold = defaults.object(forKey: Key.randomPrintThreshold) as? Int
 
-            if defaults.bool(forKey: Key.legacyHasPromptedAfterFirstPurchase) {
-                state.firstPurchasePromptCompleted = true
-                defaults.removeObject(forKey: Key.legacyHasPromptedAfterFirstPurchase)
+            if let timestamp = defaults.object(forKey: Key.firstLaunchDate) as? TimeInterval {
+                state.firstLaunchDate = Date(timeIntervalSince1970: timestamp)
+            }
+            if let timestamp = defaults.object(forKey: Key.lastAutomaticPromptDate) as? TimeInterval {
+                state.lastAutomaticPromptDate = Date(timeIntervalSince1970: timestamp)
             }
 
-            defaults.removeObject(forKey: Key.legacyPromptedTransactionIDs)
+            clearLegacyKeys(in: defaults)
             return state
         }
 
         func save() {
             let defaults = UserDefaults.standard
-            defaults.set(firstPurchasePromptPending, forKey: Key.firstPurchasePromptPending)
-            defaults.set(firstPurchasePromptCompleted, forKey: Key.firstPurchasePromptCompleted)
-            defaults.set(usageBasedPromptCompleted, forKey: Key.usageBasedPromptCompleted)
-            defaults.set(hasRequestedReview, forKey: Key.hasRequestedReview)
-            defaults.set(accumulatedActiveUsageSeconds, forKey: Key.accumulatedActiveUsageSeconds)
+            defaults.set(launchCount, forKey: Key.launchCount)
+            defaults.set(successfulPrintCount, forKey: Key.successfulPrintCount)
+            defaults.set(randomPrintThreshold, forKey: Key.randomPrintThreshold)
+
+            if let firstLaunchDate {
+                defaults.set(firstLaunchDate.timeIntervalSince1970, forKey: Key.firstLaunchDate)
+            }
+            if let lastAutomaticPromptDate {
+                defaults.set(lastAutomaticPromptDate.timeIntervalSince1970, forKey: Key.lastAutomaticPromptDate)
+            }
+        }
+
+        private static func clearLegacyKeys(in defaults: UserDefaults) {
+            let legacyKeys = [
+                "appRating.firstPurchasePromptPending",
+                "appRating.firstPurchasePromptCompleted",
+                "appRating.usageBasedPromptCompleted",
+                "appRating.hasRequestedReview",
+                "appRating.accumulatedActiveUsageSeconds",
+                "appRating.hasPromptedAfterFirstPurchase",
+                "appRating.promptedTransactionIDs",
+            ]
+            legacyKeys.forEach { defaults.removeObject(forKey: $0) }
         }
     }
 
     private enum Key {
-        static let firstPurchasePromptPending = "appRating.firstPurchasePromptPending"
-        static let firstPurchasePromptCompleted = "appRating.firstPurchasePromptCompleted"
-        static let usageBasedPromptCompleted = "appRating.usageBasedPromptCompleted"
-        static let hasRequestedReview = "appRating.hasRequestedReview"
-        static let accumulatedActiveUsageSeconds = "appRating.accumulatedActiveUsageSeconds"
-        static let legacyHasPromptedAfterFirstPurchase = "appRating.hasPromptedAfterFirstPurchase"
-        static let legacyPromptedTransactionIDs = "appRating.promptedTransactionIDs"
+        static let launchCount = "appRating.launchCount"
+        static let successfulPrintCount = "appRating.successfulPrintCount"
+        static let firstLaunchDate = "appRating.firstLaunchDate"
+        static let lastAutomaticPromptDate = "appRating.lastAutomaticPromptDate"
+        static let randomPrintThreshold = "appRating.randomPrintThreshold"
     }
 
     private var state = State.load()
-    private var activeSessionStart: Date?
-    private var usageCheckTimer: Timer?
-    private var isShowingPrompt = false
-    private var pendingPromptWorkItem: DispatchWorkItem?
     private var observers: [NSObjectProtocol] = []
     private var hasStarted = false
+    private var pendingReviewWorkItem: DispatchWorkItem?
 
     private init() {}
 
@@ -97,237 +87,90 @@ final class AppRatingManager {
         guard !hasStarted else { return }
         hasStarted = true
 
-        let center = NotificationCenter.default
-        observers.append(
-            center.addObserver(
-                forName: NSApplication.didBecomeActiveNotification,
-                object: nil,
-                queue: .main
-            ) { [weak self] _ in
-                Task { @MainActor in self?.applicationDidBecomeActive() }
-            }
-        )
-        observers.append(
-            center.addObserver(
-                forName: NSApplication.willResignActiveNotification,
-                object: nil,
-                queue: .main
-            ) { [weak self] _ in
-                Task { @MainActor in self?.applicationWillResignActive() }
-            }
-        )
-        observers.append(
-            center.addObserver(
-                forName: .premiumStatusDidChange,
-                object: nil,
-                queue: .main
-            ) { [weak self] _ in
-                Task { @MainActor in self?.premiumStatusDidChange() }
-            }
-        )
-
-        if NSApp.isActive {
-            applicationDidBecomeActive()
-        }
-    }
-
-    /// Asks premium users on their first purchase whether they like the app.
-    func promptAfterFirstPurchaseIfNeeded(on window: NSWindow?) {
-        guard isEligibleForAnyPrompt else { return }
-        guard canShowFirstPurchasePrompt else { return }
-
-        state.firstPurchasePromptPending = true
-        state.save()
-        presentPrompt(source: .firstPurchase, on: window ?? presentationWindow())
-    }
-
-    // MARK: - App lifecycle
-
-    private func applicationDidBecomeActive() {
-        resumeUsageTrackingIfNeeded()
-        scheduleDeferredPromptChecks()
-    }
-
-    private func applicationWillResignActive() {
-        commitActiveUsageSession()
-        stopUsageCheckTimer()
-        pendingPromptWorkItem?.cancel()
-        pendingPromptWorkItem = nil
-    }
-
-    private func premiumStatusDidChange() {
-        if StoreManager.shared.isPremium {
-            resumeUsageTrackingIfNeeded()
-            scheduleDeferredPromptChecks()
-        } else {
-            commitActiveUsageSession()
-            stopUsageCheckTimer()
-        }
-    }
-
-    // MARK: - Eligibility
-
-    private var isEligibleForAnyPrompt: Bool {
-        StoreManager.shared.isPremium && !state.hasRequestedReview
-    }
-
-    private var canShowFirstPurchasePrompt: Bool {
-        isEligibleForAnyPrompt && !state.firstPurchasePromptCompleted
-    }
-
-    private var canShowUsageBasedPrompt: Bool {
-        isEligibleForAnyPrompt
-            && state.firstPurchasePromptCompleted
-            && !state.usageBasedPromptCompleted
-            && currentAccumulatedUsageSeconds >= Constants.usageThresholdSeconds
-    }
-
-    private var shouldTrackUsage: Bool {
-        isEligibleForAnyPrompt
-            && state.firstPurchasePromptCompleted
-            && !state.usageBasedPromptCompleted
-    }
-
-    private var currentAccumulatedUsageSeconds: TimeInterval {
-        var total = state.accumulatedActiveUsageSeconds
-        if let activeSessionStart {
-            total += Date().timeIntervalSince(activeSessionStart)
+        if state.firstLaunchDate == nil {
+            state.firstLaunchDate = Date()
         }
-        return total
-    }
-
-    // MARK: - Usage tracking
-
-    private func resumeUsageTrackingIfNeeded() {
-        guard shouldTrackUsage else {
-            commitActiveUsageSession()
-            stopUsageCheckTimer()
-            return
+        if state.randomPrintThreshold == nil {
+            state.randomPrintThreshold = Int.random(in: Constants.printThresholdRange)
         }
 
-        if activeSessionStart == nil {
-            activeSessionStart = Date()
+        if isPremiumUser {
+            state.launchCount += 1
         }
-        startUsageCheckTimer()
-    }
-
-    private func commitActiveUsageSession() {
-        guard let sessionStart = activeSessionStart else { return }
-        let elapsed = max(0, Date().timeIntervalSince(sessionStart))
-        activeSessionStart = nil
-        guard elapsed > 0 else { return }
-
-        state.accumulatedActiveUsageSeconds += elapsed
         state.save()
     }
 
-    private func startUsageCheckTimer() {
-        guard usageCheckTimer == nil else { return }
+    /// Called after the user successfully sends a print job — a natural positive moment.
+    func recordSuccessfulPrint(from window: NSWindow?) {
+        guard isPremiumUser else { return }
 
-        let timer = Timer(timeInterval: Constants.usageCheckInterval, repeats: true) { [weak self] _ in
-            Task { @MainActor in
-                self?.evaluateUsageMilestonePrompt()
-            }
-        }
-        RunLoop.main.add(timer, forMode: .common)
-        usageCheckTimer = timer
+        state.successfulPrintCount += 1
+        state.save()
+        considerAutomaticReview(from: window)
     }
 
-    private func stopUsageCheckTimer() {
-        usageCheckTimer?.invalidate()
-        usageCheckTimer = nil
-    }
+    /// User-initiated rating from Settings. Premium users only.
+    func requestReviewFromUser(on window: NSWindow?) {
+        guard isPremiumUser else { return }
 
-    private func evaluateUsageMilestonePrompt() {
-        guard shouldTrackUsage else {
-            stopUsageCheckTimer()
+        if let reviewURL = AppStoreConfig.appStoreReviewURL {
+            NSWorkspace.shared.open(reviewURL)
             return
         }
 
-        guard currentAccumulatedUsageSeconds >= Constants.usageThresholdSeconds else { return }
-        presentPrompt(source: .usageMilestone, on: presentationWindow())
+        requestAppStoreReview(from: window)
     }
 
-    // MARK: - Prompt presentation
+    // MARK: - Automatic review
 
-    private func scheduleDeferredPromptChecks() {
-        pendingPromptWorkItem?.cancel()
+    private var isPremiumUser: Bool {
+        StoreManager.shared.isPremium
+    }
 
-        let workItem = DispatchWorkItem { [weak self] in
-            guard let self else { return }
-            if self.canShowFirstPurchasePrompt, self.state.firstPurchasePromptPending {
-                self.presentPrompt(source: .firstPurchase, on: self.presentationWindow())
-                return
-            }
-            self.evaluateUsageMilestonePrompt()
-        }
+    private func considerAutomaticReview(from window: NSWindow?) {
+        guard meetsAutomaticEligibility else { return }
+        guard Double.random(in: 0..<1) < Constants.automaticPromptProbability else { return }
 
-        pendingPromptWorkItem = workItem
-        DispatchQueue.main.asyncAfter(deadline: .now() + Constants.promptPresentationDelay, execute: workItem)
+        state.lastAutomaticPromptDate = Date()
+        state.save()
+        scheduleReviewRequest(from: window)
     }
 
-    private func presentPrompt(source: PromptSource, on window: NSWindow?) {
-        guard !isShowingPrompt else { return }
-
-        switch source {
-        case .firstPurchase:
-            guard canShowFirstPurchasePrompt else { return }
-        case .usageMilestone:
-            guard canShowUsageBasedPrompt else { return }
-            commitActiveUsageSession()
-            stopUsageCheckTimer()
-        }
+    private var meetsAutomaticEligibility: Bool {
+        guard isPremiumUser else { return false }
+        guard state.launchCount >= Constants.minLaunches else { return false }
 
-        isShowingPrompt = true
+        guard let firstLaunchDate = state.firstLaunchDate else { return false }
+        guard Date().timeIntervalSince(firstLaunchDate) >= Constants.minDaysSinceInstall else { return false }
 
-        let alert = NSAlert()
-        alert.messageText = source.title
-        alert.informativeText = source.message
-        alert.alertStyle = .informational
-        alert.addButton(withTitle: "Yes")
-        alert.addButton(withTitle: "Not Really")
+        let printThreshold = state.randomPrintThreshold ?? Constants.printThresholdRange.lowerBound
+        guard state.successfulPrintCount >= printThreshold else { return false }
 
-        let handleResponse: (NSApplication.ModalResponse) -> Void = { [weak self] response in
-            self?.handlePromptResponse(source: source, response: response, window: window)
+        if let lastPromptDate = state.lastAutomaticPromptDate,
+           Date().timeIntervalSince(lastPromptDate) < Constants.cooldownBetweenPrompts {
+            return false
         }
 
-        if let window {
-            alert.beginSheetModal(for: window) { response in
-                handleResponse(response)
-            }
-        } else {
-            handleResponse(alert.runModal())
-        }
+        return true
     }
 
-    private func handlePromptResponse(
-        source: PromptSource,
-        response: NSApplication.ModalResponse,
-        window: NSWindow?
-    ) {
-        isShowingPrompt = false
-
-        switch source {
-        case .firstPurchase:
-            state.firstPurchasePromptPending = false
-            state.firstPurchasePromptCompleted = true
-        case .usageMilestone:
-            state.usageBasedPromptCompleted = true
-            stopUsageCheckTimer()
-        }
+    private func scheduleReviewRequest(from window: NSWindow?) {
+        pendingReviewWorkItem?.cancel()
 
-        if response == .alertFirstButtonReturn {
-            state.hasRequestedReview = true
-            state.save()
-            requestAppStoreReview(from: window)
-            return
+        let workItem = DispatchWorkItem { [weak self] in
+            guard let self, self.isPremiumUser else { return }
+            self.requestAppStoreReview(from: window)
         }
-
-        state.save()
-        resumeUsageTrackingIfNeeded()
+        pendingReviewWorkItem = workItem
+        DispatchQueue.main.asyncAfter(
+            deadline: .now() + Constants.reviewPresentationDelay,
+            execute: workItem
+        )
     }
 
     private func requestAppStoreReview(from window: NSWindow?) {
+        guard isPremiumUser else { return }
+
         let viewController = window?.contentViewController
             ?? presentationWindow()?.contentViewController
             ?? NSApp.keyWindow?.contentViewController

+ 10 - 0
smart_printer/AppStoreConfig.swift

@@ -5,6 +5,16 @@ import Foundation
 enum AppStoreConfig {
     static let bundleIdentifier = "com.smartprinter"
 
+    /// Numeric Mac App Store ID from App Store Connect (General → App Information → Apple ID).
+    /// Example: "1234567890". Leave empty until the app is live on the App Store.
+    static let appStoreID = ""
+
+    /// Direct write-review link for the manual Settings action.
+    static var appStoreReviewURL: URL? {
+        guard !appStoreID.isEmpty else { return nil }
+        return URL(string: "https://apps.apple.com/app/id\(appStoreID)?action=write-review")
+    }
+
     enum ProductID {
         static let monthly = "\(bundleIdentifier).premium.monthly"
         static let yearly = "\(bundleIdentifier).premium.yearly"

+ 2 - 4
smart_printer/PaywallView.swift

@@ -1751,8 +1751,8 @@ final class PaywallView: NSView, AppearanceRefreshable {
                 let outcome = try await StoreManager.shared.purchase(plan: selectedPlan)
                 refreshStoreState()
                 switch outcome {
-                case .purchased:
-                    AppRatingManager.shared.promptAfterFirstPurchaseIfNeeded(on: window)
+                case .purchased, .cancelled:
+                    break
                 case .pending:
                     let messages = paywallConfig.messages
                     StoreManager.shared.showAlert(
@@ -1760,8 +1760,6 @@ final class PaywallView: NSView, AppearanceRefreshable {
                         message: messages.pendingPurchaseMessage,
                         on: window
                     )
-                case .cancelled:
-                    break
                 }
             } catch {
                 refreshPurchaseState()

+ 29 - 2
smart_printer/PrintService.swift

@@ -298,10 +298,37 @@ enum PrintService {
 
         let observer = observePrintPanelAppearance(relativeTo: parentWindow)
         defer { NotificationCenter.default.removeObserver(observer) }
+
         if let parentWindow {
-            operation.runModal(for: parentWindow, delegate: nil, didRun: nil, contextInfo: nil)
+            let callback = PrintRatingCallback(window: parentWindow)
+            operation.runModal(
+                for: parentWindow,
+                delegate: callback,
+                didRun: #selector(PrintRatingCallback.printOperationDidRun(_:success:contextInfo:)),
+                contextInfo: nil
+            )
+            withExtendedLifetime(callback) {}
         } else {
-            _ = operation.run()
+            operation.run()
+        }
+    }
+
+    private final class PrintRatingCallback: NSObject {
+        private let window: NSWindow?
+
+        init(window: NSWindow?) {
+            self.window = window
+        }
+
+        @objc func printOperationDidRun(
+            _ printOperation: NSPrintOperation,
+            success: Bool,
+            contextInfo: UnsafeMutableRawPointer?
+        ) {
+            guard success else { return }
+            Task { @MainActor in
+                AppRatingManager.shared.recordSuccessfulPrint(from: window)
+            }
         }
     }
 

+ 53 - 7
smart_printer/SettingsView.swift

@@ -3,6 +3,9 @@ import Cocoa
 // MARK: - Root
 
 final class SettingsView: NSView, AppearanceRefreshable {
+    private var aboutCard: SettingsGroupCard!
+    private var observers: [NSObjectProtocol] = []
+
     init() {
         super.init(frame: .zero)
         wantsLayer = true
@@ -14,10 +17,18 @@ final class SettingsView: NSView, AppearanceRefreshable {
     @available(*, unavailable)
     required init?(coder: NSCoder) { nil }
 
+    deinit {
+        observers.forEach { NotificationCenter.default.removeObserver($0) }
+    }
+
     func refreshAppearance() {
         layer?.backgroundColor = AppTheme.background.cgColor
     }
 
+    func refreshPremiumState() {
+        refreshAboutCard()
+    }
+
     private func setup() {
         let scrollView = NSScrollView()
         scrollView.translatesAutoresizingMaskIntoConstraints = false
@@ -39,10 +50,21 @@ final class SettingsView: NSView, AppearanceRefreshable {
         stack.translatesAutoresizingMaskIntoConstraints = false
 
         let appSection = makeSection(title: "App", card: makeAppCard())
-        let aboutSection = makeSection(title: "About", card: makeAboutCard())
+        aboutCard = makeAboutCard()
+        let aboutSection = makeSection(title: "About", card: aboutCard)
         stack.addArrangedSubview(appSection)
         stack.addArrangedSubview(aboutSection)
 
+        observers.append(
+            NotificationCenter.default.addObserver(
+                forName: .premiumStatusDidChange,
+                object: nil,
+                queue: .main
+            ) { [weak self] _ in
+                self?.refreshAboutCard()
+            }
+        )
+
         [appSection, aboutSection].forEach { section in
             section.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
         }
@@ -117,23 +139,39 @@ final class SettingsView: NSView, AppearanceRefreshable {
         return card
     }
 
-    private func makeAboutCard() -> NSView {
+    private func makeAboutCard() -> SettingsGroupCard {
         let card = SettingsGroupCard()
+        card.setRows(aboutCardRows())
+        return card
+    }
 
-        card.addRow(SettingsActionRow(symbolName: "link", title: "Website") {
+    private func refreshAboutCard() {
+        aboutCard?.setRows(aboutCardRows())
+    }
+
+    private func aboutCardRows() -> [NSView] {
+        var rows: [NSView] = []
+
+        if StoreManager.shared.isPremium {
+            rows.append(SettingsActionRow(symbolName: "star.fill", title: "Rate App") {
+                AppRatingManager.shared.requestReviewFromUser(on: NSApp.keyWindow)
+            })
+        }
+
+        rows.append(SettingsActionRow(symbolName: "link", title: "Website") {
             NSWorkspace.shared.open(URL(string: "https://sites.google.com/view/smartprinterappmacos/home")!)
         })
-        card.addRow(SettingsActionRow(symbolName: "questionmark.circle", title: "Support") {
+        rows.append(SettingsActionRow(symbolName: "questionmark.circle", title: "Support") {
             NSWorkspace.shared.open(URL(string: "https://sites.google.com/view/smartprinterappmacos/get-support")!)
         })
-        card.addRow(SettingsActionRow(symbolName: "doc.text", title: "Terms of Use") {
+        rows.append(SettingsActionRow(symbolName: "doc.text", title: "Terms of Use") {
             NSWorkspace.shared.open(URL(string: "https://sites.google.com/view/smartprinterappmacos/terms-and-condition")!)
         })
-        card.addRow(SettingsActionRow(symbolName: "shield", title: "Privacy Policy", isLast: true) {
+        rows.append(SettingsActionRow(symbolName: "shield", title: "Privacy Policy", isLast: true) {
             NSWorkspace.shared.open(URL(string: "https://sites.google.com/view/smartprinterappmacos/privacy-policy")!)
         })
 
-        return card
+        return rows
     }
 }
 
@@ -170,6 +208,14 @@ private final class SettingsGroupCard: NSView, AppearanceRefreshable {
         row.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
     }
 
+    func setRows(_ rows: [NSView]) {
+        stack.arrangedSubviews.forEach { row in
+            stack.removeArrangedSubview(row)
+            row.removeFromSuperview()
+        }
+        rows.forEach { addRow($0) }
+    }
+
     func refreshAppearance() {
         layer?.backgroundColor = AppTheme.groupCardBackground.cgColor
         layer?.borderColor = AppTheme.paywallBorder.cgColor

+ 3 - 0
smart_printer/ViewController.swift

@@ -14,6 +14,7 @@ class ViewController: NSViewController {
     private var printContentView: NSView!
     private var scanAndHomeContentView: NSView!
     private var settingsContentView: NSView!
+    private weak var settingsView: SettingsView?
     private var contentContainer: NSView!
     private var paywallOverlay: PaywallOverlayView?
     private var photoPreviewOverlay: PhotoPreviewOverlayView?
@@ -77,6 +78,7 @@ class ViewController: NSViewController {
         Task { @MainActor in
             paywallOverlay?.refreshStoreState()
             sidebar?.refreshPremiumState()
+            settingsView?.refreshPremiumState()
             if StoreManager.shared.isPremium {
                 navigateHomeAfterPremiumUnlock()
             }
@@ -661,6 +663,7 @@ class ViewController: NSViewController {
         let container = NSView()
         container.translatesAutoresizingMaskIntoConstraints = false
         let settingsView = SettingsView()
+        self.settingsView = settingsView
         container.addSubview(settingsView)
 
         NSLayoutConstraint.activate([