@@ -44,6 +44,7 @@ protocol OptionsButtonMenuDelegate: AnyObject {
44
44
func optionsButtonMenuRequestedDataBrokerProtection( _ menu: NSMenu )
45
45
#endif
46
46
func optionsButtonMenuRequestedSubscriptionPurchasePage( _ menu: NSMenu )
47
+ func optionsButtonMenuRequestedSubscriptionPreferences( _ menu: NSMenu )
47
48
func optionsButtonMenuRequestedIdentityTheftRestoration( _ menu: NSMenu )
48
49
}
49
50
@@ -57,7 +58,8 @@ final class MoreOptionsMenu: NSMenu {
57
58
private let passwordManagerCoordinator : PasswordManagerCoordinating
58
59
private let internalUserDecider : InternalUserDecider
59
60
private lazy var sharingMenu : NSMenu = SharingMenu ( title: UserText . shareMenuItem)
60
- private let accountManager : AccountManager
61
+ private var accountManager : AccountManager { subscriptionManager. accountManager }
62
+ private let subscriptionManager : SubscriptionManager
61
63
62
64
private let vpnFeatureGatekeeper : VPNFeatureGatekeeper
63
65
private let subscriptionFeatureAvailability : SubscriptionFeatureAvailability
@@ -73,15 +75,15 @@ final class MoreOptionsMenu: NSMenu {
73
75
subscriptionFeatureAvailability: SubscriptionFeatureAvailability = DefaultSubscriptionFeatureAvailability ( ) ,
74
76
sharingMenu: NSMenu ? = nil ,
75
77
internalUserDecider: InternalUserDecider ,
76
- accountManager : AccountManager ) {
78
+ subscriptionManager : SubscriptionManager ) {
77
79
78
80
self . tabCollectionViewModel = tabCollectionViewModel
79
81
self . emailManager = emailManager
80
82
self . passwordManagerCoordinator = passwordManagerCoordinator
81
83
self . vpnFeatureGatekeeper = vpnFeatureGatekeeper
82
84
self . subscriptionFeatureAvailability = subscriptionFeatureAvailability
83
85
self . internalUserDecider = internalUserDecider
84
- self . accountManager = accountManager
86
+ self . subscriptionManager = subscriptionManager
85
87
86
88
super. init ( title: " " )
87
89
@@ -234,6 +236,10 @@ final class MoreOptionsMenu: NSMenu {
234
236
actionDelegate? . optionsButtonMenuRequestedSubscriptionPurchasePage ( self )
235
237
}
236
238
239
+ @objc func openSubscriptionSettings( _ sender: NSMenuItem ) {
240
+ actionDelegate? . optionsButtonMenuRequestedSubscriptionPreferences ( self )
241
+ }
242
+
237
243
@objc func openIdentityTheftRestoration( _ sender: NSMenuItem ) {
238
244
actionDelegate? . optionsButtonMenuRequestedIdentityTheftRestoration ( self )
239
245
}
@@ -294,119 +300,31 @@ final class MoreOptionsMenu: NSMenu {
294
300
}
295
301
296
302
private func addSubscriptionItems( ) {
297
- var items : [ NSMenuItem ] = [ ]
298
-
299
- if subscriptionFeatureAvailability. isFeatureAvailable && !accountManager. isUserAuthenticated {
300
- items. append ( contentsOf: makeInactiveSubscriptionItems ( ) )
301
- } else {
302
- items. append ( contentsOf: makeActiveSubscriptionItems ( ) ) // this adds NETP and DBP only if conditionally enabled
303
- }
304
-
305
- if !items. isEmpty {
306
- items. forEach { addItem ( $0) }
307
- addItem ( NSMenuItem . separator ( ) )
308
- }
309
- }
303
+ guard subscriptionFeatureAvailability. isFeatureAvailable else { return }
310
304
311
- // swiftlint:disable:next cyclomatic_complexity function_body_length
312
- private func makeActiveSubscriptionItems( ) -> [ NSMenuItem ] {
313
- var items : [ NSMenuItem ] = [ ]
314
-
315
- let networkProtectionItem : NSMenuItem
316
-
317
- networkProtectionItem = makeNetworkProtectionItem ( )
318
-
319
- items. append ( networkProtectionItem)
320
-
321
- if subscriptionFeatureAvailability. isFeatureAvailable && accountManager. isUserAuthenticated {
322
- Task {
323
- let isMenuItemEnabled : Bool
324
-
325
- switch await accountManager. hasEntitlement ( forProductName: . networkProtection) {
326
- case let . success( result) :
327
- isMenuItemEnabled = result
328
- case . failure:
329
- isMenuItemEnabled = false
330
- }
331
-
332
- networkProtectionItem. isEnabled = isMenuItemEnabled
333
- }
305
+ func shouldHideDueToNoProduct( ) -> Bool {
306
+ let platform = subscriptionManager. currentEnvironment. purchasePlatform
307
+ return platform == . appStore && subscriptionManager. canPurchase == false
334
308
}
335
309
336
- #if DBP
337
- let dbpGatekeeper = DefaultDataBrokerProtectionFeatureGatekeeper ( accountManager: accountManager)
338
- if dbpGatekeeper. isFeatureVisible ( ) || dbpGatekeeper. isPrivacyProEnabled ( ) {
339
- let dataBrokerProtectionItem = NSMenuItem ( title: UserText . dataBrokerProtectionOptionsMenuItem,
340
- action: #selector( openDataBrokerProtection) ,
341
- keyEquivalent: " " )
342
- . targetting ( self )
343
- . withImage ( . dbpIcon)
344
- items. append ( dataBrokerProtectionItem)
345
-
346
- if subscriptionFeatureAvailability. isFeatureAvailable && accountManager. isUserAuthenticated {
347
- Task {
348
- let isMenuItemEnabled : Bool
310
+ let privacyProItem = NSMenuItem ( title: UserText . subscriptionOptionsMenuItem) . withImage ( . subscriptionIcon)
349
311
350
- switch await accountManager. hasEntitlement ( forProductName: . dataBrokerProtection) {
351
- case let . success( result) :
352
- isMenuItemEnabled = result
353
- case . failure:
354
- isMenuItemEnabled = false
355
- }
312
+ if !accountManager. isUserAuthenticated {
313
+ privacyProItem. target = self
314
+ privacyProItem. action = #selector( openSubscriptionPurchasePage ( _: ) )
356
315
357
- dataBrokerProtectionItem. isEnabled = isMenuItemEnabled
358
- }
316
+ // Do not add for App Store when purchase not available in the region
317
+ if !shouldHideDueToNoProduct( ) {
318
+ addItem ( privacyProItem)
319
+ addItem ( NSMenuItem . separator ( ) )
359
320
}
360
-
361
- DataBrokerProtectionExternalWaitlistPixels . fire ( pixel: GeneralPixel . dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: . dailyAndCount)
362
-
363
321
} else {
364
- dbpGatekeeper. disableAndDeleteForWaitlistUsers ( )
365
- }
366
- #endif // DBP
367
-
368
- if accountManager. isUserAuthenticated {
369
- let identityTheftRestorationItem = NSMenuItem ( title: UserText . identityTheftRestorationOptionsMenuItem,
370
- action: #selector( openIdentityTheftRestoration) ,
371
- keyEquivalent: " " )
372
- . targetting ( self )
373
- . withImage ( . itrIcon)
374
- items. append ( identityTheftRestorationItem)
375
-
376
- if subscriptionFeatureAvailability. isFeatureAvailable && accountManager. isUserAuthenticated {
377
- Task {
378
- let isMenuItemEnabled : Bool
379
-
380
- switch await accountManager. hasEntitlement ( forProductName: . identityTheftRestoration) {
381
- case let . success( result) :
382
- isMenuItemEnabled = result
383
- case . failure:
384
- isMenuItemEnabled = false
385
- }
386
-
387
- identityTheftRestorationItem. isEnabled = isMenuItemEnabled
388
- }
389
- }
322
+ privacyProItem. submenu = SubscriptionSubMenu ( targeting: self ,
323
+ subscriptionFeatureAvailability: DefaultSubscriptionFeatureAvailability ( ) ,
324
+ accountManager: accountManager)
325
+ addItem ( privacyProItem)
326
+ addItem ( NSMenuItem . separator ( ) )
390
327
}
391
-
392
- return items
393
- }
394
-
395
- private func makeInactiveSubscriptionItems( ) -> [ NSMenuItem ] {
396
- let subscriptionManager = Application . appDelegate. subscriptionManager
397
- let platform = subscriptionManager. currentEnvironment. purchasePlatform
398
- let shouldHidePrivacyProDueToNoProducts = platform == . appStore && subscriptionManager. canPurchase == false
399
- if shouldHidePrivacyProDueToNoProducts {
400
- return [ ]
401
- }
402
-
403
- let privacyProItem = NSMenuItem ( title: UserText . subscriptionOptionsMenuItem,
404
- action: #selector( openSubscriptionPurchasePage ( _: ) ) ,
405
- keyEquivalent: " " )
406
- . targetting ( self )
407
- . withImage ( . subscriptionIcon)
408
-
409
- return [ privacyProItem]
410
328
}
411
329
412
330
private func addPageItems( ) {
@@ -770,4 +688,112 @@ final class LoginsSubMenu: NSMenu {
770
688
771
689
}
772
690
691
+ @MainActor
692
+ final class SubscriptionSubMenu : NSMenu , NSMenuDelegate {
693
+
694
+ var subscriptionFeatureAvailability : SubscriptionFeatureAvailability
695
+ var accountManager : AccountManager
696
+
697
+ var networkProtectionItem : NSMenuItem !
698
+ var dataBrokerProtectionItem : NSMenuItem !
699
+ var identityTheftRestorationItem : NSMenuItem !
700
+ var subscriptionSettingsItem : NSMenuItem !
701
+
702
+ init ( targeting target: AnyObject ,
703
+ subscriptionFeatureAvailability: SubscriptionFeatureAvailability ,
704
+ accountManager: AccountManager ) {
705
+
706
+ self . subscriptionFeatureAvailability = subscriptionFeatureAvailability
707
+ self . accountManager = accountManager
708
+
709
+ super. init ( title: " " )
710
+
711
+ self . networkProtectionItem = makeNetworkProtectionItem ( target: target)
712
+ self . dataBrokerProtectionItem = makeDataBrokerProtectionItem ( target: target)
713
+ self . identityTheftRestorationItem = makeIdentityTheftRestorationItem ( target: target)
714
+ self . subscriptionSettingsItem = makeSubscriptionSettingsItem ( target: target)
715
+
716
+ delegate = self
717
+
718
+ addMenuItems ( )
719
+ }
720
+
721
+ required init ( coder: NSCoder ) {
722
+ fatalError ( " init(coder:) has not been implemented " )
723
+ }
724
+
725
+ private func addMenuItems( ) {
726
+ addItem ( networkProtectionItem)
727
+ addItem ( dataBrokerProtectionItem)
728
+ addItem ( identityTheftRestorationItem)
729
+ addItem ( NSMenuItem . separator ( ) )
730
+ addItem ( subscriptionSettingsItem)
731
+ }
732
+
733
+ private func makeNetworkProtectionItem( target: AnyObject ) -> NSMenuItem {
734
+ return NSMenuItem ( title: UserText . networkProtection,
735
+ action: #selector( MoreOptionsMenu . showNetworkProtectionStatus ( _: ) ) ,
736
+ keyEquivalent: " " )
737
+ . targetting ( target)
738
+ . withImage ( . image( for: . vpnIcon) )
739
+ }
740
+
741
+ private func makeDataBrokerProtectionItem( target: AnyObject ) -> NSMenuItem {
742
+ return NSMenuItem ( title: UserText . dataBrokerProtectionOptionsMenuItem,
743
+ action: #selector( MoreOptionsMenu . openDataBrokerProtection) ,
744
+ keyEquivalent: " " )
745
+ . targetting ( target)
746
+ . withImage ( . dbpIcon)
747
+ }
748
+
749
+ private func makeIdentityTheftRestorationItem( target: AnyObject ) -> NSMenuItem {
750
+ return NSMenuItem ( title: UserText . identityTheftRestorationOptionsMenuItem,
751
+ action: #selector( MoreOptionsMenu . openIdentityTheftRestoration) ,
752
+ keyEquivalent: " " )
753
+ . targetting ( target)
754
+ . withImage ( . itrIcon)
755
+ }
756
+
757
+ private func makeSubscriptionSettingsItem( target: AnyObject ) -> NSMenuItem {
758
+ return NSMenuItem ( title: UserText . subscriptionSettingsOptionsMenuItem,
759
+ action: #selector( MoreOptionsMenu . openSubscriptionSettings) ,
760
+ keyEquivalent: " " )
761
+ . targetting ( target)
762
+ }
763
+
764
+ private func refreshAvailabilityBasedOnEntitlements( ) {
765
+ guard subscriptionFeatureAvailability. isFeatureAvailable, accountManager. isUserAuthenticated else { return }
766
+
767
+ @Sendable func hasEntitlement( for productName: Entitlement . ProductName ) async -> Bool {
768
+ switch await self . accountManager. hasEntitlement ( forProductName: productName) {
769
+ case let . success( result) :
770
+ return result
771
+ case . failure:
772
+ return false
773
+ }
774
+ }
775
+
776
+ Task . detached ( priority: . background) { [ weak self] in
777
+ guard let self else { return }
778
+
779
+ let isNetworkProtectionItemEnabled = await hasEntitlement ( for: . networkProtection)
780
+ let isDataBrokerProtectionItemEnabled = await hasEntitlement ( for: . dataBrokerProtection)
781
+ let isIdentityTheftRestorationItemEnabled = await hasEntitlement ( for: . identityTheftRestoration)
782
+
783
+ Task { @MainActor in
784
+ self . networkProtectionItem. isEnabled = isNetworkProtectionItemEnabled
785
+ self . dataBrokerProtectionItem. isEnabled = isDataBrokerProtectionItemEnabled
786
+ self . identityTheftRestorationItem. isEnabled = isIdentityTheftRestorationItemEnabled
787
+
788
+ DataBrokerProtectionExternalWaitlistPixels . fire ( pixel: GeneralPixel . dataBrokerProtectionWaitlistEntryPointMenuItemDisplayed, frequency: . dailyAndCount)
789
+ }
790
+ }
791
+ }
792
+
793
+ public func menuWillOpen( _ menu: NSMenu ) {
794
+ refreshAvailabilityBasedOnEntitlements ( )
795
+ }
796
+
797
+ }
798
+
773
799
extension MoreOptionsMenu : EmailManagerRequestDelegate { }
0 commit comments