Explorar o código

Refine settings upgrade flow for premium users.

Show Upgrade only for premium non-lifetime accounts, route it into a lifetime-focused paywall state, and surface the current active plan so users can continue to upgrade.

Made-with: Cursor
huzaifahayat12 hai 1 semana
pai
achega
d993315608
Modificáronse 1 ficheiros con 60 adicións e 4 borrados
  1. 60 4
      meetings_app/ViewController.swift

+ 60 - 4
meetings_app/ViewController.swift

@@ -30,6 +30,7 @@ private enum SettingsAction: Int {
30
     case support = 2
30
     case support = 2
31
     case moreApps = 3
31
     case moreApps = 3
32
     case shareApp = 4
32
     case shareApp = 4
33
+    case upgrade = 5
33
 }
34
 }
34
 
35
 
35
 private enum PremiumPlan: Int {
36
 private enum PremiumPlan: Int {
@@ -84,6 +85,13 @@ private final class StoreKitCoordinator {
84
     var onEntitlementsChanged: ((Bool) -> Void)?
85
     var onEntitlementsChanged: ((Bool) -> Void)?
85
 
86
 
86
     var hasPremiumAccess: Bool { !activeEntitlementProductIDs.isEmpty }
87
     var hasPremiumAccess: Bool { !activeEntitlementProductIDs.isEmpty }
88
+    var hasLifetimeAccess: Bool { activeEntitlementProductIDs.contains(PremiumStoreProduct.lifetime) }
89
+    var activeNonLifetimePlan: PremiumPlan? {
90
+        activeEntitlementProductIDs
91
+            .compactMap { PremiumStoreProduct.plan(for: $0) }
92
+            .filter { $0 != .lifetime }
93
+            .max(by: { $0.rawValue < $1.rawValue })
94
+    }
87
 
95
 
88
     private var transactionUpdatesTask: Task<Void, Never>?
96
     private var transactionUpdatesTask: Task<Void, Never>?
89
 
97
 
@@ -266,6 +274,7 @@ final class ViewController: NSViewController {
266
     private var paywallPriceLabels: [PremiumPlan: NSTextField] = [:]
274
     private var paywallPriceLabels: [PremiumPlan: NSTextField] = [:]
267
     private var paywallSubtitleLabels: [PremiumPlan: NSTextField] = [:]
275
     private var paywallSubtitleLabels: [PremiumPlan: NSTextField] = [:]
268
     private var paywallContinueEnabled = true
276
     private var paywallContinueEnabled = true
277
+    private var paywallUpgradeFlowEnabled = false
269
     private var hasCompletedInitialStoreKitSync = false
278
     private var hasCompletedInitialStoreKitSync = false
270
     private var hasPresentedLaunchPaywall = false
279
     private var hasPresentedLaunchPaywall = false
271
     private var hasViewAppearedOnce = false
280
     private var hasViewAppearedOnce = false
@@ -316,10 +325,12 @@ final class ViewController: NSViewController {
316
         let popover = NSPopover()
325
         let popover = NSPopover()
317
         popover.behavior = .transient
326
         popover.behavior = .transient
318
         popover.animates = true
327
         popover.animates = true
328
+        let showUpgradeInSettings = storeKitCoordinator.hasPremiumAccess && !storeKitCoordinator.hasLifetimeAccess
319
         popover.contentViewController = SettingsMenuViewController(
329
         popover.contentViewController = SettingsMenuViewController(
320
             palette: palette,
330
             palette: palette,
321
             typography: typography,
331
             typography: typography,
322
             darkModeEnabled: darkModeEnabled,
332
             darkModeEnabled: darkModeEnabled,
333
+            showUpgradeInSettings: showUpgradeInSettings,
323
             onToggleDarkMode: { [weak self] enabled in
334
             onToggleDarkMode: { [weak self] enabled in
324
                 self?.setDarkMode(enabled)
335
                 self?.setDarkMode(enabled)
325
             },
336
             },
@@ -737,6 +748,10 @@ private extension ViewController {
737
             NSPasteboard.general.clearContents()
748
             NSPasteboard.general.clearContents()
738
             NSPasteboard.general.setString(urlString, forType: .string)
749
             NSPasteboard.general.setString(urlString, forType: .string)
739
             showSimpleAlert(title: "Share App", message: "Link copied to clipboard:\n\(urlString)")
750
             showSimpleAlert(title: "Share App", message: "Link copied to clipboard:\n\(urlString)")
751
+        case .upgrade:
752
+            settingsPopover?.performClose(nil)
753
+            settingsPopover = nil
754
+            showPaywall(upgradeFlow: true, preferredPlan: .lifetime)
740
         }
755
         }
741
     }
756
     }
742
 
757
 
@@ -748,8 +763,13 @@ private extension ViewController {
748
         alert.runModal()
763
         alert.runModal()
749
     }
764
     }
750
 
765
 
751
-    private func showPaywall() {
766
+    private func showPaywall(upgradeFlow: Bool = false, preferredPlan: PremiumPlan? = nil) {
767
+        paywallUpgradeFlowEnabled = upgradeFlow
768
+        if let preferredPlan {
769
+            selectedPremiumPlan = preferredPlan
770
+        }
752
         if let existing = paywallWindow {
771
         if let existing = paywallWindow {
772
+            refreshPaywallStoreUI()
753
             animatePaywallPresentation(existing)
773
             animatePaywallPresentation(existing)
754
             existing.makeKeyAndOrderFront(nil)
774
             existing.makeKeyAndOrderFront(nil)
755
             NSApp.activate(ignoringOtherApps: true)
775
             NSApp.activate(ignoringOtherApps: true)
@@ -866,6 +886,16 @@ private extension ViewController {
866
 
886
 
867
     private func paywallOfferText(for plan: PremiumPlan) -> String {
887
     private func paywallOfferText(for plan: PremiumPlan) -> String {
868
         if storeKitCoordinator.hasPremiumAccess {
888
         if storeKitCoordinator.hasPremiumAccess {
889
+            if storeKitCoordinator.hasLifetimeAccess {
890
+                return "Lifetime premium is active on this Apple ID."
891
+            }
892
+            if paywallUpgradeFlowEnabled {
893
+                let currentPlanName = storeKitCoordinator.activeNonLifetimePlan?.displayName ?? "Premium"
894
+                if plan == .lifetime {
895
+                    return "Current plan: \(currentPlanName). Tap Continue to upgrade to Lifetime."
896
+                }
897
+                return "Current plan: \(currentPlanName). Select Lifetime to upgrade."
898
+            }
869
             return "Premium is active on this Apple ID."
899
             return "Premium is active on this Apple ID."
870
         }
900
         }
871
         let productID = PremiumStoreProduct.productID(for: plan)
901
         let productID = PremiumStoreProduct.productID(for: plan)
@@ -1052,10 +1082,20 @@ private extension ViewController {
1052
             paywallContinueButton?.alphaValue = 0.75
1082
             paywallContinueButton?.alphaValue = 0.75
1053
             return
1083
             return
1054
         }
1084
         }
1055
-        if storeKitCoordinator.hasPremiumAccess {
1085
+        if storeKitCoordinator.hasLifetimeAccess {
1056
             paywallContinueEnabled = false
1086
             paywallContinueEnabled = false
1057
             paywallContinueLabel?.stringValue = "Premium Active"
1087
             paywallContinueLabel?.stringValue = "Premium Active"
1058
             paywallContinueButton?.alphaValue = 0.75
1088
             paywallContinueButton?.alphaValue = 0.75
1089
+        } else if paywallUpgradeFlowEnabled && storeKitCoordinator.hasPremiumAccess {
1090
+            if selectedPremiumPlan == .lifetime {
1091
+                paywallContinueEnabled = true
1092
+                paywallContinueLabel?.stringValue = "Continue"
1093
+                paywallContinueButton?.alphaValue = 1.0
1094
+            } else {
1095
+                paywallContinueEnabled = false
1096
+                paywallContinueLabel?.stringValue = "Select Lifetime to Upgrade"
1097
+                paywallContinueButton?.alphaValue = 0.75
1098
+            }
1059
         } else {
1099
         } else {
1060
             paywallContinueEnabled = true
1100
             paywallContinueEnabled = true
1061
             paywallContinueLabel?.stringValue = "Continue"
1101
             paywallContinueLabel?.stringValue = "Continue"
@@ -2736,6 +2776,17 @@ private extension ViewController {
2736
     }
2776
     }
2737
 }
2777
 }
2738
 
2778
 
2779
+private extension PremiumPlan {
2780
+    var displayName: String {
2781
+        switch self {
2782
+        case .weekly: return "Weekly"
2783
+        case .monthly: return "Monthly"
2784
+        case .yearly: return "Yearly"
2785
+        case .lifetime: return "Lifetime"
2786
+        }
2787
+    }
2788
+}
2789
+
2739
 extension ViewController: NSTextFieldDelegate {
2790
 extension ViewController: NSTextFieldDelegate {
2740
     func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
2791
     func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
2741
         if control === browseAddressField, commandSelector == #selector(NSResponder.insertNewline(_:)) {
2792
         if control === browseAddressField, commandSelector == #selector(NSResponder.insertNewline(_:)) {
@@ -2751,6 +2802,7 @@ extension ViewController: NSWindowDelegate {
2751
         guard let closingWindow = notification.object as? NSWindow else { return }
2802
         guard let closingWindow = notification.object as? NSWindow else { return }
2752
         if closingWindow === paywallWindow {
2803
         if closingWindow === paywallWindow {
2753
             paywallWindow = nil
2804
             paywallWindow = nil
2805
+            paywallUpgradeFlowEnabled = false
2754
         }
2806
         }
2755
     }
2807
     }
2756
 }
2808
 }
@@ -3258,6 +3310,7 @@ private final class SettingsMenuViewController: NSViewController {
3258
         palette: Palette,
3310
         palette: Palette,
3259
         typography: Typography,
3311
         typography: Typography,
3260
         darkModeEnabled: Bool,
3312
         darkModeEnabled: Bool,
3313
+        showUpgradeInSettings: Bool,
3261
         onToggleDarkMode: @escaping (Bool) -> Void,
3314
         onToggleDarkMode: @escaping (Bool) -> Void,
3262
         onAction: @escaping (SettingsAction) -> Void
3315
         onAction: @escaping (SettingsAction) -> Void
3263
     ) {
3316
     ) {
@@ -3266,7 +3319,7 @@ private final class SettingsMenuViewController: NSViewController {
3266
         self.onToggleDarkMode = onToggleDarkMode
3319
         self.onToggleDarkMode = onToggleDarkMode
3267
         self.onAction = onAction
3320
         self.onAction = onAction
3268
         super.init(nibName: nil, bundle: nil)
3321
         super.init(nibName: nil, bundle: nil)
3269
-        self.view = makeView(darkModeEnabled: darkModeEnabled)
3322
+        self.view = makeView(darkModeEnabled: darkModeEnabled, showUpgradeInSettings: showUpgradeInSettings)
3270
     }
3323
     }
3271
 
3324
 
3272
     @available(*, unavailable)
3325
     @available(*, unavailable)
@@ -3278,7 +3331,7 @@ private final class SettingsMenuViewController: NSViewController {
3278
         darkToggle?.state = enabled ? .on : .off
3331
         darkToggle?.state = enabled ? .on : .off
3279
     }
3332
     }
3280
 
3333
 
3281
-    private func makeView(darkModeEnabled: Bool) -> NSView {
3334
+    private func makeView(darkModeEnabled: Bool, showUpgradeInSettings: Bool) -> NSView {
3282
         let root = NSView()
3335
         let root = NSView()
3283
         root.translatesAutoresizingMaskIntoConstraints = false
3336
         root.translatesAutoresizingMaskIntoConstraints = false
3284
 
3337
 
@@ -3312,6 +3365,9 @@ private final class SettingsMenuViewController: NSViewController {
3312
         stack.addArrangedSubview(settingsActionRow(icon: "💬", title: "Support", action: .support))
3365
         stack.addArrangedSubview(settingsActionRow(icon: "💬", title: "Support", action: .support))
3313
         stack.addArrangedSubview(settingsActionRow(icon: "⋯", title: "More Apps", action: .moreApps))
3366
         stack.addArrangedSubview(settingsActionRow(icon: "⋯", title: "More Apps", action: .moreApps))
3314
         stack.addArrangedSubview(settingsActionRow(icon: "⤴︎", title: "Share App", action: .shareApp))
3367
         stack.addArrangedSubview(settingsActionRow(icon: "⤴︎", title: "Share App", action: .shareApp))
3368
+        if showUpgradeInSettings {
3369
+            stack.addArrangedSubview(settingsActionRow(icon: "⬆︎", title: "Upgrade", action: .upgrade))
3370
+        }
3315
 
3371
 
3316
         for v in stack.arrangedSubviews {
3372
         for v in stack.arrangedSubviews {
3317
             v.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true
3373
             v.widthAnchor.constraint(equalTo: stack.widthAnchor).isActive = true