Skip to content

Commit 5d390ed

Browse files
authored
Merge pull request #5299 from woocommerce/issue/5032-new-push-notification-payload
Support new push notification payload format for multi-store push notifications
2 parents 320d981 + da1d4b5 commit 5d390ed

File tree

13 files changed

+329
-59
lines changed

13 files changed

+329
-59
lines changed

Experiments/Experiments/DefaultFeatureFlagService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct DefaultFeatureFlagService: FeatureFlagService {
2020
case .shippingLabelsMultiPackage:
2121
return true
2222
case .pushNotificationsForAllStores:
23-
return buildConfig == .localDeveloper || buildConfig == .alpha
23+
return true
2424
case .simplePaymentsPrototype:
2525
return buildConfig == .localDeveloper || buildConfig == .alpha
2626
case .orderListFilters:

Networking/Networking/Remote/DevicesRemote.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class DevicesRemote: Remote {
1212
/// - applicationId: App ID.
1313
/// - applicationVersion: App Version.
1414
/// - defaultStoreID: Active Store ID.
15-
/// - completion: Closure to be executed on commpletion.
15+
/// - completion: Closure to be executed on completion.
1616
///
1717
public func registerDevice(device: APNSDevice,
1818
applicationId: String,
@@ -48,7 +48,7 @@ public class DevicesRemote: Remote {
4848
///
4949
/// - Parameters:
5050
/// - deviceId: Identifier of the device to be removed.
51-
/// - completion: Closure to be executed on commpletion.
51+
/// - completion: Closure to be executed on completion.
5252
///
5353
public func unregisterDevice(deviceId: String, completion: @escaping (Error?) -> Void) {
5454
let path = String(format: Paths.delete, deviceId)

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
8.0
44
-----
5+
- [***] Push notifications are now supported for all connected stores. [https://github.com/woocommerce/woocommerce-ios/pull/5299]
56
- [*] Fix: in Settings > Switch Store, tapping "Dismiss" after selecting a different store does not switch stores anymore. [https://github.com/woocommerce/woocommerce-ios/pull/5359]
67

78
7.9

WooCommerce/Classes/Notifications/ApplicationAdapter.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ protocol ApplicationAdapter: AnyObject {
1818
///
1919
func registerForRemoteNotifications()
2020

21-
/// Presents a given Message with an "In App" notification
21+
/// Presents a given title and optional subtitle and message with an "In App" notification
2222
///
23-
func presentInAppNotification(message: String)
23+
func presentInAppNotification(title: String, subtitle: String?, message: String?)
2424

2525
/// Presents the Details for the specified Notification.
2626
///
@@ -40,8 +40,8 @@ extension UIApplication: ApplicationAdapter {
4040

4141
/// Presents a given Message with an "In App" notification
4242
///
43-
func presentInAppNotification(message: String) {
44-
let notice = Notice(title: message, message: nil, feedbackType: .success)
43+
func presentInAppNotification(title: String, subtitle: String?, message: String?) {
44+
let notice = Notice(title: title, subtitle: subtitle, message: message, feedbackType: .success)
4545
ServiceLocator.noticePresenter.enqueue(notice: notice)
4646
}
4747
}

WooCommerce/Classes/Notifications/PushNotificationsManager.swift

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Experiments
12
import Foundation
23
import UserNotifications
34
import AutomatticTracks
@@ -70,12 +71,16 @@ final class PushNotificationsManager: PushNotesManager {
7071
configuration.storesManager
7172
}
7273

74+
private let featureFlagService: FeatureFlagService
75+
7376
/// Initializes the PushNotificationsManager.
7477
///
7578
/// - Parameter configuration: PushNotificationsConfiguration Instance that should be used.
79+
/// - Parameter featureFlagService: called for multi-store push notifications feature.
7680
///
77-
init(configuration: PushNotificationsConfiguration = .default) {
81+
init(configuration: PushNotificationsConfiguration = .default, featureFlagService: FeatureFlagService = ServiceLocator.featureFlagService) {
7882
self.configuration = configuration
83+
self.featureFlagService = featureFlagService
7984
}
8085
}
8186

@@ -345,8 +350,13 @@ private extension PushNotificationsManager {
345350
return false
346351
}
347352

348-
if let foregroundNotification = PushNotification.from(userInfo: userInfo) {
349-
configuration.application.presentInAppNotification(message: foregroundNotification.message)
353+
let pushNotificationsForAllStoresEnabled = featureFlagService.isFeatureFlagEnabled(.pushNotificationsForAllStores)
354+
if let foregroundNotification = PushNotification.from(userInfo: userInfo,
355+
pushNotificationsForAllStoresEnabled: pushNotificationsForAllStoresEnabled) {
356+
configuration.application
357+
.presentInAppNotification(title: foregroundNotification.title,
358+
subtitle: foregroundNotification.subtitle,
359+
message: foregroundNotification.message)
350360

351361
foregroundNotificationsSubject.send(foregroundNotification)
352362
}
@@ -372,7 +382,9 @@ private extension PushNotificationsManager {
372382

373383
DDLogVerbose("📱 Handling Notification in Inactive State")
374384

375-
if let notification = PushNotification.from(userInfo: userInfo) {
385+
let pushNotificationsForAllStoresEnabled = featureFlagService.isFeatureFlagEnabled(.pushNotificationsForAllStores)
386+
if let notification = PushNotification.from(userInfo: userInfo,
387+
pushNotificationsForAllStoresEnabled: pushNotificationsForAllStoresEnabled) {
376388

377389
// Handling the product review notifications (`.comment`) has been moved to
378390
// `ReviewsCoordinator`. All other push notification handling should be in a coordinator
@@ -418,7 +430,7 @@ private extension PushNotificationsManager {
418430
///
419431
func registerDotcomDevice(with deviceToken: String, defaultStoreID: Int64, onCompletion: @escaping (DotcomDevice?, Error?) -> Void) {
420432
let device = APNSDevice(deviceToken: deviceToken)
421-
let pushNotificationsForAllStoresEnabled = ServiceLocator.featureFlagService.isFeatureFlagEnabled(.pushNotificationsForAllStores)
433+
let pushNotificationsForAllStoresEnabled = featureFlagService.isFeatureFlagEnabled(.pushNotificationsForAllStores)
422434
let action = NotificationAction.registerDevice(device: device,
423435
applicationId: WooConstants.pushApplicationID,
424436
applicationVersion: Bundle.main.version,
@@ -519,15 +531,27 @@ private extension PushNotificationsManager {
519531
// MARK: - PushNotification Extension
520532

521533
private extension PushNotification {
522-
static func from(userInfo: [AnyHashable: Any]) -> PushNotification? {
523-
guard let noteID = userInfo.integer(forKey: APNSKey.identifier),
524-
let message = userInfo.dictionary(forKey: APNSKey.aps)?.string(forKey: APNSKey.alert),
525-
let type = userInfo.string(forKey: APNSKey.type),
526-
let noteKind = Note.Kind(rawValue: type) else {
527-
return nil
534+
static func from(userInfo: [AnyHashable: Any], pushNotificationsForAllStoresEnabled: Bool) -> PushNotification? {
535+
if pushNotificationsForAllStoresEnabled {
536+
guard let noteID = userInfo.integer(forKey: APNSKey.identifier),
537+
let alert = userInfo.dictionary(forKey: APNSKey.aps)?.dictionary(forKey: APNSKey.alert),
538+
let title = alert.string(forKey: APNSKey.alertTitle),
539+
let type = userInfo.string(forKey: APNSKey.type),
540+
let noteKind = Note.Kind(rawValue: type) else {
541+
return nil
542+
}
543+
let subtitle = alert.string(forKey: APNSKey.alertSubtitle)
544+
let message = alert.string(forKey: APNSKey.alertMessage)
545+
return PushNotification(noteID: noteID, kind: noteKind, title: title, subtitle: subtitle, message: message)
546+
} else {
547+
guard let noteID = userInfo.integer(forKey: APNSKey.identifier),
548+
let title = userInfo.dictionary(forKey: APNSKey.aps)?.string(forKey: APNSKey.alert),
549+
let type = userInfo.string(forKey: APNSKey.type),
550+
let noteKind = Note.Kind(rawValue: type) else {
551+
return nil
552+
}
553+
return PushNotification(noteID: noteID, kind: noteKind, title: title, subtitle: nil, message: nil)
528554
}
529-
530-
return PushNotification(noteID: noteID, kind: noteKind, message: message)
531555
}
532556
}
533557

@@ -547,6 +571,9 @@ enum AppIconBadgeNumber {
547571
private enum APNSKey {
548572
static let aps = "aps"
549573
static let alert = "alert"
574+
static let alertTitle = "title"
575+
static let alertSubtitle = "subtitle"
576+
static let alertMessage = "body"
550577
static let identifier = "note_id"
551578
static let type = "type"
552579
static let siteID = "blog"

WooCommerce/Classes/ServiceLocator/PushNotification.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ struct PushNotification {
1212
/// The `type` value received from the Remote Notification's `userInfo`.
1313
///
1414
let kind: Note.Kind
15-
/// The `alert` value received from the Remote Notification's `userInfo`.
15+
/// The `alert.title` value received from the Remote Notification's `userInfo`.
1616
///
17-
let message: String
17+
let title: String
18+
/// The `alert.subtitle` value received from the Remote Notification's `userInfo`.
19+
///
20+
let subtitle: String?
21+
/// The `alert.message` value received from the Remote Notification's `userInfo`.
22+
///
23+
let message: String?
1824
}

WooCommerce/Classes/Tools/Notices/Notice.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ import UIKit
77
///
88
struct Notice {
99

10-
/// The title of the notice
10+
/// The title that contains the reason for the notice
1111
///
1212
let title: String
1313

14-
/// An optional subtitle for the notice
14+
/// An optional subtitle that contains a secondary description of the reason for the notice
15+
///
16+
let subtitle: String?
17+
18+
/// An optional message that contains any details for the notice
1519
///
1620
let message: String?
1721

@@ -35,12 +39,14 @@ struct Notice {
3539
/// Designated Initializer
3640
///
3741
init(title: String,
42+
subtitle: String? = nil,
3843
message: String? = nil,
3944
feedbackType: UINotificationFeedbackGenerator.FeedbackType? = nil,
4045
notificationInfo: NoticeNotificationInfo? = nil,
4146
actionTitle: String? = nil,
4247
actionHandler: ((() -> Void))? = nil) {
4348
self.title = title
49+
self.subtitle = subtitle
4450
self.message = message
4551
self.feedbackType = feedbackType
4652
self.notificationInfo = notificationInfo
@@ -52,6 +58,7 @@ struct Notice {
5258
extension Notice: Equatable {
5359
static func == (lhs: Notice, rhs: Notice) -> Bool {
5460
return lhs.title == rhs.title &&
61+
lhs.subtitle == rhs.subtitle &&
5562
lhs.message == rhs.message &&
5663
lhs.feedbackType == rhs.feedbackType &&
5764
lhs.notificationInfo?.identifier == rhs.notificationInfo?.identifier &&

WooCommerce/Classes/Tools/Notices/NoticeView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class NoticeView: UIView {
1313
private let shadowMaskLayer = CAShapeLayer()
1414

1515
private let titleLabel = UILabel()
16+
private let subtitleLabel = UILabel()
1617
private let messageLabel = UILabel()
1718
private let actionButton = UIButton(type: .system)
1819

@@ -117,6 +118,7 @@ private extension NoticeView {
117118
labelStackView.layoutMargins = Metrics.layoutMargins
118119

119120
labelStackView.addArrangedSubview(titleLabel)
121+
labelStackView.addArrangedSubview(subtitleLabel)
120122
labelStackView.addArrangedSubview(messageLabel)
121123

122124
contentStackView.addArrangedSubview(labelStackView)
@@ -127,9 +129,11 @@ private extension NoticeView {
127129
])
128130

129131
titleLabel.font = Fonts.titleLabelFont
132+
subtitleLabel.font = Fonts.subtitleLabelFont
130133
messageLabel.font = Fonts.messageLabelFont
131134

132135
titleLabel.textColor = Appearance.titleColor
136+
subtitleLabel.textColor = Appearance.titleColor
133137
messageLabel.textColor = Appearance.titleColor
134138
}
135139

@@ -164,6 +168,13 @@ private extension NoticeView {
164168
func configureForNotice() {
165169
titleLabel.text = notice.title
166170

171+
if let subtitle = notice.subtitle {
172+
subtitleLabel.isHidden = false
173+
subtitleLabel.text = subtitle
174+
} else {
175+
subtitleLabel.isHidden = true
176+
}
177+
167178
if let message = notice.message {
168179
messageLabel.text = message
169180
messageLabel.numberOfLines = 0
@@ -208,6 +219,7 @@ private extension NoticeView {
208219
enum Fonts {
209220
static let actionButtonFont = UIFont.systemFont(ofSize: 14.0)
210221
static let titleLabelFont = UIFont.boldSystemFont(ofSize: 14.0)
222+
static let subtitleLabelFont = UIFont.boldSystemFont(ofSize: 14.0)
211223
static let messageLabelFont = UIFont.systemFont(ofSize: 14.0)
212224
}
213225

WooCommerce/WooCommerceTests/Mocks/MockApplicationAdapter.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import UIKit
55

66
/// MockApplicationAdapter: UIApplication Mock!
77
///
8-
class MockApplicationAdapter: ApplicationAdapter {
8+
final class MockApplicationAdapter: ApplicationAdapter {
99

1010
/// Badge Count
1111
///
@@ -19,9 +19,9 @@ class MockApplicationAdapter: ApplicationAdapter {
1919
///
2020
var registerWasCalled = false
2121

22-
/// Messages received via the `presentInAppNotification` method.
22+
/// Title, subtitle, and message tuples received via the `presentInAppNotification` method.
2323
///
24-
var presentInAppMessages = [String]()
24+
var presentInAppMessages = [(title: String, subtitle: String?, message: String?)]()
2525

2626
/// Notification Identifiers received via the `presentNotificationDetails` method.
2727
///
@@ -37,8 +37,8 @@ class MockApplicationAdapter: ApplicationAdapter {
3737

3838
/// Innocuous `presentInAppNotification`
3939
///
40-
func presentInAppNotification(message: String) {
41-
presentInAppMessages.append(message)
40+
func presentInAppNotification(title: String, subtitle: String?, message: String?) {
41+
presentInAppMessages.append((title: title, subtitle: subtitle, message: message))
4242
}
4343

4444
/// Innocuous `displayNotificationDetails`

0 commit comments

Comments
 (0)