Skip to content

Commit 48633ce

Browse files
authored
Jetpack Focus: Create foundations to Disable Notifications For WordPress app when Jetpack notifications enabled (#19531)
* Added remote feature flag allowDisablingWPNotifications * Added enable notification switch to NotificationSettings under a feature flag * Created NotificationFilteringService to contain logic required to disable notifications
1 parent 6725201 commit 48633ce

File tree

9 files changed

+286
-1
lines changed

9 files changed

+286
-1
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import Foundation
2+
3+
/// The service is created to support disabling WordPress notifications when Jetpack app is installed
4+
/// The service uses App Groups which allows Jetpack app to change the state of notifications flag and be later accessed by WordPress app
5+
/// This is a temporary solution to avoid duplicate notifications during the migration process from WordPress to Jetpack app
6+
/// This service and its usage can be deleted once the migration is done
7+
final class NotificationFilteringService {
8+
private var notificationSettingsLoader: NotificationSettingsLoader
9+
private var notificationsEnabled: Bool = false
10+
private let allowDisablingWPNotifications: Bool
11+
private let isWordPress: Bool
12+
private let userDefaults = UserDefaults(suiteName: WPAppGroupName)
13+
14+
var wordPressNotificationsEnabled: Bool {
15+
get {
16+
guard let userDefaults = userDefaults,
17+
userDefaults.value(forKey: WPNotificationsEnabledKey) != nil else {
18+
/// Treat this flag as enabled if it wasn't explicitly disabled
19+
return true
20+
}
21+
22+
return userDefaults.bool(forKey: WPNotificationsEnabledKey)
23+
}
24+
25+
set {
26+
userDefaults?.set(newValue, forKey: WPNotificationsEnabledKey)
27+
}
28+
}
29+
30+
init(notificationSettingsLoader: NotificationSettingsLoader = UNUserNotificationCenter.current(),
31+
allowDisablingWPNotifications: Bool = FeatureFlag.allowDisablingWPNotifications.enabled,
32+
isWordPress: Bool = AppConfiguration.isWordPress) {
33+
self.notificationSettingsLoader = notificationSettingsLoader
34+
self.allowDisablingWPNotifications = allowDisablingWPNotifications
35+
self.isWordPress = isWordPress
36+
37+
notificationSettingsLoader.getNotificationAuthorizationStatus { [weak self] status in
38+
self?.notificationsEnabled = status == .authorized
39+
}
40+
}
41+
42+
func shouldShowNotificationControl() -> Bool {
43+
return allowDisablingWPNotifications && isWordPress && notificationsEnabled
44+
}
45+
46+
func disableWordPressNotificationsIfNeeded() {
47+
if allowDisablingWPNotifications, !isWordPress {
48+
wordPressNotificationsEnabled = false
49+
}
50+
}
51+
52+
func shouldFilterWordPressNotifications() -> Bool {
53+
return allowDisablingWPNotifications
54+
&& isWordPress
55+
&& !wordPressNotificationsEnabled
56+
}
57+
}
58+
59+
// MARK: - Helpers
60+
61+
protocol NotificationSettingsLoader: AnyObject {
62+
func getNotificationAuthorizationStatus(completionHandler: @escaping (UNAuthorizationStatus) -> Void)
63+
}
64+
65+
extension UNUserNotificationCenter: NotificationSettingsLoader {
66+
func getNotificationAuthorizationStatus(completionHandler: @escaping (UNAuthorizationStatus) -> Void) {
67+
getNotificationSettings { settings in
68+
completionHandler(settings.authorizationStatus)
69+
}
70+
}
71+
}

WordPress/Classes/System/Constants.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ extern NSString *const WPNotificationServiceExtensionKeychainServiceName;
4141
extern NSString *const WPNotificationServiceExtensionKeychainTokenKey;
4242
extern NSString *const WPNotificationServiceExtensionKeychainUsernameKey;
4343
extern NSString *const WPNotificationServiceExtensionKeychainUserIDKey;
44+
extern NSString *const WPNotificationsEnabledKey;
4445

4546
/// Share Extension Constants
4647
///

WordPress/Classes/System/Constants.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
NSString *const WPNotificationServiceExtensionKeychainTokenKey = @"OAuth2Token";
4848
NSString *const WPNotificationServiceExtensionKeychainUsernameKey = @"Username";
4949
NSString *const WPNotificationServiceExtensionKeychainUserIDKey = @"UserID";
50+
NSString *const WPNotificationsEnabledKey = @"WordPressNotificationsEnabled";
5051

5152
/// Share Extension Constants
5253
///

WordPress/Classes/Utility/BuildInformation/FeatureFlag.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
3737
case newJetpackLandingScreen
3838
case newWordPressLandingScreen
3939
case newCoreDataContext
40+
case allowDisablingWPNotifications
4041
case jetpackFeaturesRemovalPhaseOne
4142
case jetpackFeaturesRemovalPhaseTwo
4243
case jetpackFeaturesRemovalPhaseThree
@@ -124,6 +125,8 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
124125
return false
125126
case .newCoreDataContext:
126127
return true
128+
case .allowDisablingWPNotifications:
129+
return false
127130
case .jetpackFeaturesRemovalPhaseOne:
128131
return false
129132
case .jetpackFeaturesRemovalPhaseTwo:
@@ -154,6 +157,8 @@ enum FeatureFlag: Int, CaseIterable, OverrideableFlag {
154157
return "jp_removal_four"
155158
case .jetpackFeaturesRemovalPhaseNewUsers:
156159
return "jp_removal_new_users"
160+
case .allowDisablingWPNotifications:
161+
return "prevent_duplicate_notifs_remote_field"
157162
default:
158163
return nil
159164
}
@@ -244,6 +249,8 @@ extension FeatureFlag {
244249
return "New WordPress landing screen"
245250
case .newCoreDataContext:
246251
return "Use new Core Data context structure (Require app restart)"
252+
case .allowDisablingWPNotifications:
253+
return "Disable WordPress app notifications when Jetpack is installed"
247254
case .jetpackFeaturesRemovalPhaseOne:
248255
return "Jetpack Features Removal Phase One"
249256
case .jetpackFeaturesRemovalPhaseTwo:

WordPress/Classes/Utility/InteractiveNotificationsManager.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,11 @@ final class InteractiveNotificationsManager: NSObject {
6060
let options: UNAuthorizationOptions = [.badge, .sound, .alert, .providesAppNotificationSettings]
6161

6262
let notificationCenter = UNUserNotificationCenter.current()
63-
notificationCenter.requestAuthorization(options: options) { (allowed, _) in
63+
notificationCenter.requestAuthorization(options: options) { [weak self] (allowed, _) in
6464
DispatchQueue.main.async {
6565
if allowed {
6666
WPAnalytics.track(.pushNotificationOSAlertAllowed)
67+
self?.disableWordPressNotificationsIfNeeded()
6768
} else {
6869
WPAnalytics.track(.pushNotificationOSAlertDenied)
6970
}
@@ -687,3 +688,12 @@ extension InteractiveNotificationsManager: UNUserNotificationCenterDelegate {
687688
MeNavigationAction.notificationSettings.perform(router: UniversalLinkRouter.shared)
688689
}
689690
}
691+
692+
private extension InteractiveNotificationsManager {
693+
/// A temporary setting to allow controlling WordPress notifications when they are disabled after Jetpack installation
694+
/// Disable WordPress notifications when they are enabled on Jetpack
695+
func disableWordPressNotificationsIfNeeded() {
696+
let notificationFilteringService = NotificationFilteringService()
697+
notificationFilteringService.disableWordPressNotificationsIfNeeded()
698+
}
699+
}

WordPress/Classes/ViewRelated/Notifications/Controllers/NotificationSettingsViewController.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class NotificationSettingsViewController: UIViewController {
3838
fileprivate let blogRowHeight = CGFloat(54.0)
3939

4040
fileprivate let defaultReuseIdentifier = WPTableViewCell.classNameWithoutNamespaces()
41+
fileprivate let switchReuseIdentifier = SwitchTableViewCell.classNameWithoutNamespaces()
4142

4243
fileprivate let emptyCount = 0
4344
fileprivate let loadMoreRowIndex = 3
@@ -52,6 +53,9 @@ class NotificationSettingsViewController: UIViewController {
5253
fileprivate var followedSites: [ReaderSiteTopic] = []
5354
fileprivate var tableSections: [Section] = []
5455

56+
/// A temporary service to allow controlling WordPress notifications when they are disabled after Jetpack installation
57+
private let notificationFilteringService = NotificationFilteringService()
58+
5559
override func loadView() {
5660
mainView.pinSubviewToAllEdges(tableView)
5761
mainView.pinSubviewAtCenter(activityIndicatorView)
@@ -99,6 +103,7 @@ class NotificationSettingsViewController: UIViewController {
99103
// Register the cells
100104
tableView.register(WPBlogTableViewCell.self, forCellReuseIdentifier: blogReuseIdentifier)
101105
tableView.register(WPTableViewCell.self, forCellReuseIdentifier: defaultReuseIdentifier)
106+
tableView.register(SwitchTableViewCell.self, forCellReuseIdentifier: switchReuseIdentifier)
102107
tableView.dataSource = self
103108
tableView.delegate = self
104109

@@ -195,6 +200,11 @@ class NotificationSettingsViewController: UIViewController {
195200
} else if !followedSites.isEmpty && section.isEmpty && AppConfiguration.showsFollowedSitesSettings {
196201
section.append(.followedSites)
197202
}
203+
204+
if notificationFilteringService.shouldShowNotificationControl() {
205+
section.insert(.notificationControl, at: 0)
206+
}
207+
198208
tableSections = section
199209
}
200210

@@ -242,6 +252,8 @@ extension NotificationSettingsViewController: UITableViewDataSource {
242252
return displayBlogMoreWasAccepted ? rowCountForBlogSection + 1 : loadMoreRowCount
243253
case .followedSites:
244254
return displayFollowedMoreWasAccepted ? rowCountForFollowedSite + 1 : min(loadMoreRowCount, rowCountForFollowedSite)
255+
case .notificationControl:
256+
return 1
245257
default:
246258
return groupedSettings[section]?.count ?? 0
247259
}
@@ -346,6 +358,8 @@ private extension NotificationSettingsViewController {
346358
switch section(at: indexPath.section) {
347359
case .blog where !isPaginationRow(indexPath), .followedSites where !isPaginationRow(indexPath):
348360
return blogReuseIdentifier
361+
case .notificationControl:
362+
return switchReuseIdentifier
349363
default:
350364
return defaultReuseIdentifier
351365
}
@@ -376,6 +390,11 @@ private extension NotificationSettingsViewController {
376390
return
377391
}
378392

393+
if let cell = cell as? SwitchTableViewCell {
394+
configureNotificationSwitchCell(cell)
395+
return
396+
}
397+
379398
// Proceed rendering the settings
380399
guard let settings = settingsForRowAtIndexPath(indexPath) else {
381400
return
@@ -443,6 +462,7 @@ private extension NotificationSettingsViewController {
443462
case followedSites
444463
case other
445464
case wordPressCom
465+
case notificationControl
446466

447467
func headerText() -> String? {
448468
switch self {
@@ -454,6 +474,8 @@ private extension NotificationSettingsViewController {
454474
return NSLocalizedString("Other", comment: "Displayed in the Notification Settings View")
455475
case .wordPressCom:
456476
return nil
477+
case .notificationControl:
478+
return nil
457479
}
458480
}
459481

@@ -471,6 +493,9 @@ private extension NotificationSettingsViewController {
471493
return NSLocalizedString("We’ll always send important emails regarding your account, " +
472494
"but you can get some helpful extras, too.",
473495
comment: "Title displayed in the Notification Settings for WordPress.com")
496+
case .notificationControl:
497+
return NSLocalizedString("Disable push notifications on the app.",
498+
comment: "Notification Settings for the app")
474499
}
475500
}
476501

@@ -621,3 +646,14 @@ extension NotificationSettingsViewController: SearchableActivityConvertable {
621646
return Set(keywordArray)
622647
}
623648
}
649+
650+
// MARK: - Notification Switch Cell
651+
extension NotificationSettingsViewController {
652+
private func configureNotificationSwitchCell(_ cell: SwitchTableViewCell) {
653+
cell.name = NSLocalizedString("Allow Notifications", comment: "Title for a cell with switch control that allows to enable or disable notifications")
654+
cell.on = notificationFilteringService.wordPressNotificationsEnabled
655+
cell.onChange = { [weak self] (newValue: Bool) in
656+
self?.notificationFilteringService.wordPressNotificationsEnabled = newValue
657+
}
658+
}
659+
}

WordPress/WordPress.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@
4343
/* End PBXAggregateTarget section */
4444

4545
/* Begin PBXBuildFile section */
46+
010459E629153FFF000C7778 /* NotificationFilteringService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010459E529153FFF000C7778 /* NotificationFilteringService.swift */; };
47+
010459E729153FFF000C7778 /* NotificationFilteringService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010459E529153FFF000C7778 /* NotificationFilteringService.swift */; };
48+
010459E82915477C000C7778 /* NotificationFilteringService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010459E529153FFF000C7778 /* NotificationFilteringService.swift */; };
49+
010459EB2915503E000C7778 /* NotificationFilteringService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010459E529153FFF000C7778 /* NotificationFilteringService.swift */; };
50+
010459ED2915519C000C7778 /* NotificationFilteringServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 010459EC2915519C000C7778 /* NotificationFilteringServiceTests.swift */; };
4651
0107E0B428F97D5000DE87DB /* Constants.m in Sources */ = {isa = PBXBuildFile; fileRef = B5CC05F51962150600975CAC /* Constants.m */; };
4752
0107E0B528F97D5000DE87DB /* StatsWidgetEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F63B93B258179D100F581BE /* StatsWidgetEntry.swift */; };
4853
0107E0B628F97D5000DE87DB /* HomeWidgetCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FA53E9B256571D800F4D9A2 /* HomeWidgetCache.swift */; };
@@ -5394,6 +5399,8 @@
53945399
/* End PBXCopyFilesBuildPhase section */
53955400

53965401
/* Begin PBXFileReference section */
5402+
010459E529153FFF000C7778 /* NotificationFilteringService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringService.swift; sourceTree = "<group>"; };
5403+
010459EC2915519C000C7778 /* NotificationFilteringServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationFilteringServiceTests.swift; sourceTree = "<group>"; };
53975404
0107E0EA28F97D5000DE87DB /* JetpackStatsWidgets.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JetpackStatsWidgets.appex; sourceTree = BUILT_PRODUCTS_DIR; };
53985405
0107E15428FE9DB200DE87DB /* JetpackIntents.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = JetpackIntents.appex; sourceTree = BUILT_PRODUCTS_DIR; };
53995406
0107E15C28FFE99300DE87DB /* WidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WidgetConfiguration.swift; sourceTree = "<group>"; };
@@ -13147,6 +13154,7 @@
1314713154
93DEB88119E5BF7100F9546D /* TodayExtensionService.m */,
1314813155
E6311C401EC9FF4A00122529 /* UsersService.swift */,
1314913156
B543D2B420570B5A00D3D4CC /* WordPressComSyncService.swift */,
13157+
010459E529153FFF000C7778 /* NotificationFilteringService.swift */,
1315013158
);
1315113159
path = Services;
1315213160
sourceTree = "<group>";
@@ -14405,6 +14413,7 @@
1440514413
46B30B772582C7DD00A25E66 /* SiteAddressServiceTests.swift */,
1440614414
17FC0031264D728E00FCBD37 /* SharingServiceTests.swift */,
1440714415
FE2E3728281C839C00A1E82A /* BloggingPromptsServiceTests.swift */,
14416+
010459EC2915519C000C7778 /* NotificationFilteringServiceTests.swift */,
1440814417
);
1440914418
name = Services;
1441014419
sourceTree = "<group>";
@@ -20909,6 +20918,7 @@
2090920918
3FB34ADA25672AA5001A74A6 /* HomeWidgetTodayData.swift in Sources */,
2091020919
172E27D31FD98135003EA321 /* NoticePresenter.swift in Sources */,
2091120920
3F43704428932F0100475B6E /* JetpackBrandingCoordinator.swift in Sources */,
20921+
010459E629153FFF000C7778 /* NotificationFilteringService.swift in Sources */,
2091220922
E61084C21B9B47BA008050C5 /* ReaderTagTopic.swift in Sources */,
2091320923
F110239B2318479000C4E84A /* Media.swift in Sources */,
2091420924
176CE91627FB44C100F1E32B /* StatsBaseCell.swift in Sources */,
@@ -21475,6 +21485,7 @@
2147521485
73E40D8C21238C520012ABA6 /* Tracks+ServiceExtension.swift in Sources */,
2147621486
73768B6B212B4E4F005136A1 /* UNNotificationContent+RemoteNotification.swift in Sources */,
2147721487
73F6DD45212C714F00CE447D /* RichNotificationViewModel.swift in Sources */,
21488+
010459E82915477C000C7778 /* NotificationFilteringService.swift in Sources */,
2147821489
73EDC70A212E5D6700E5E3ED /* RemoteNotificationStyles.swift in Sources */,
2147921490
);
2148021491
runOnlyForDeploymentPostprocessing = 0;
@@ -21749,6 +21760,7 @@
2174921760
80F6D04628EE866A00953C1A /* Tracks+ServiceExtension.swift in Sources */,
2175021761
80F6D04728EE866A00953C1A /* UNNotificationContent+RemoteNotification.swift in Sources */,
2175121762
80F6D04828EE866A00953C1A /* RichNotificationViewModel.swift in Sources */,
21763+
010459EB2915503E000C7778 /* NotificationFilteringService.swift in Sources */,
2175221764
80F6D04928EE866A00953C1A /* RemoteNotificationStyles.swift in Sources */,
2175321765
);
2175421766
runOnlyForDeploymentPostprocessing = 0;
@@ -22080,6 +22092,7 @@
2208022092
C81CCD6C243AEFBF00A83E27 /* TenorReponseData.swift in Sources */,
2208122093
570BFD902282418A007859A8 /* PostBuilder.swift in Sources */,
2208222094
C738CB0F28626466001BE107 /* QRLoginScanningCoordinatorTests.swift in Sources */,
22095+
010459ED2915519C000C7778 /* NotificationFilteringServiceTests.swift in Sources */,
2208322096
8B6214E627B1B446001DF7B6 /* BlogDashboardServiceTests.swift in Sources */,
2208422097
C856749A243F4292001A995E /* TenorMockDataHelper.swift in Sources */,
2208522098
3FFE3C0828FE00D10021BB96 /* StatsSegmentedControlDataTests.swift in Sources */,
@@ -23558,6 +23571,7 @@
2355823571
FABB24FB2602FC2C00C8785C /* ReaderListStreamHeader.swift in Sources */,
2355923572
FABB24FC2602FC2C00C8785C /* NoResultsViewController+MediaLibrary.swift in Sources */,
2356023573
FABB24FD2602FC2C00C8785C /* StockPhotosDataLoader.swift in Sources */,
23574+
010459E729153FFF000C7778 /* NotificationFilteringService.swift in Sources */,
2356123575
FABB24FE2602FC2C00C8785C /* ReaderTopicToReaderTagTopic37to38.swift in Sources */,
2356223576
FABB24FF2602FC2C00C8785C /* ChangeUsernameViewModel.swift in Sources */,
2356323577
FABB25002602FC2C00C8785C /* PlansLoadingIndicatorView.swift in Sources */,

WordPress/WordPressNotificationServiceExtension/Sources/NotificationService.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class NotificationService: UNNotificationServiceExtension {
2121
/// The service used to retrieve remote notifications
2222
private var notificationService: NotificationSyncServiceRemote?
2323

24+
/// A temporary service to allow controlling WordPress notifications when they are disabled after Jetpack installation
25+
private let notificationFilteringService = NotificationFilteringService()
26+
2427
// MARK: UNNotificationServiceExtension
2528

2629
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
@@ -331,3 +334,10 @@ private extension NotificationService {
331334

332335
static let viewMilestoneTitle = AppLocalizedString("You hit a milestone 🚀", comment: "Title for a view milestone push notification")
333336
}
337+
338+
// MARK: - Notification Filtering
339+
private extension NotificationService {
340+
func shouldFilterNotification() -> Bool {
341+
return notificationFilteringService.shouldFilterWordPressNotifications()
342+
}
343+
}

0 commit comments

Comments
 (0)