From e2a17d3f71edf8d5cc6b93375828b5a47898b4a1 Mon Sep 17 00:00:00 2001 From: Mateusz Wu <15673167+worotyns@users.noreply.github.com> Date: Thu, 13 Jul 2023 09:24:44 +0200 Subject: [PATCH] feature(tags): add strategy and ttl option (#7) Co-authored-by: worotyns --- PPG_framework.podspec | 2 +- .../PPG_framework.xcodeproj/project.pbxproj | 2 +- .../PPG_framework/Models/Beacon.swift | 39 ++++ .../PPG_framework/Models/Enums.swift | 5 + PPG_framework/PPG_framework/README.md | 186 ++++++++++++------ README.md | 33 +++- 6 files changed, 191 insertions(+), 76 deletions(-) diff --git a/PPG_framework.podspec b/PPG_framework.podspec index fbe9264..2e02d76 100644 --- a/PPG_framework.podspec +++ b/PPG_framework.podspec @@ -16,7 +16,7 @@ Pod::Spec.new do |spec| # spec.name = "PPG_framework" - spec.version = "1.1.0" + spec.version = "1.2.0" spec.summary = "PushPushGo framework" # This description is used to generate tags and improve search results. diff --git a/PPG_framework/PPG_framework.xcodeproj/project.pbxproj b/PPG_framework/PPG_framework.xcodeproj/project.pbxproj index 7423eb7..5accec2 100644 --- a/PPG_framework/PPG_framework.xcodeproj/project.pbxproj +++ b/PPG_framework/PPG_framework.xcodeproj/project.pbxproj @@ -56,7 +56,7 @@ C629360D24BDCC5B00485D4B /* ApiService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiService.swift; sourceTree = ""; }; C630D24224BDEAAD00F86145 /* SharedData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedData.swift; sourceTree = ""; }; C655331E24C034E200A5BE17 /* ApiModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiModels.swift; sourceTree = ""; }; - C655332824C050C200A5BE17 /* Beacon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Beacon.swift; sourceTree = ""; }; + C655332824C050C200A5BE17 /* Beacon.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Beacon.swift; sourceTree = ""; tabWidth = 4; }; C655332A24C050D500A5BE17 /* Event.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Event.swift; sourceTree = ""; }; C655337F24DBFB3000EABF8B /* UNNotificationAttachment+Init.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UNNotificationAttachment+Init.swift"; sourceTree = ""; }; C655338124DC30DA00EABF8B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; diff --git a/PPG_framework/PPG_framework/Models/Beacon.swift b/PPG_framework/PPG_framework/Models/Beacon.swift index a25624f..d00df1d 100644 --- a/PPG_framework/PPG_framework/Models/Beacon.swift +++ b/PPG_framework/PPG_framework/Models/Beacon.swift @@ -22,6 +22,18 @@ public class Beacon { customId = "" } + public func appendTag(_ tag: String, _ label: String, _ ttl: Int64 = 0) { + tags.append(BeaconTag(tag: tag, label: label, strategy: .append, ttl: ttl)) + } + + public func rewriteTag(_ tag: String, _ label: String, _ ttl: Int64 = 0) { + tags.append(BeaconTag(tag: tag, label: label, strategy: .rewrite, ttl: ttl)) + } + + public func deleteTag(_ tag: String, _ label: String) { + tagsToDelete.append(BeaconTag(tag: tag, label: label)) + } + public func addTag(_ tag: String, _ label: String) { tags.append(BeaconTag(tag: tag, label: label)) } @@ -82,15 +94,42 @@ public class Beacon { } } + public struct BeaconTag: Codable { public let tag: String public let label: String + public let strategy: String + public let ttl: Int64 + public init(tag: String) { + self.tag = tag + self.label = "default" + self.strategy = BeaconTagStrategy.append.rawValue + self.ttl = 0 + } + public init(tag: String, label: String) { self.tag = tag self.label = label + self.strategy = BeaconTagStrategy.append.rawValue + self.ttl = 0 } + + public init(tag: String, label: String, strategy: BeaconTagStrategy) { + self.tag = tag + self.label = label + self.strategy = strategy.rawValue + self.ttl = 0 + } + + public init(tag: String, label: String, strategy: BeaconTagStrategy, ttl: Int64) { + self.tag = tag + self.label = label + self.strategy = strategy.rawValue + self.ttl = ttl + } + } public struct BeaconSelector: Codable { diff --git a/PPG_framework/PPG_framework/Models/Enums.swift b/PPG_framework/PPG_framework/Models/Enums.swift index bce66e0..348da72 100644 --- a/PPG_framework/PPG_framework/Models/Enums.swift +++ b/PPG_framework/PPG_framework/Models/Enums.swift @@ -27,3 +27,8 @@ enum pushCategories: String { case oneButtonCategory case twoButtonsCategory } + +public enum BeaconTagStrategy: String, Codable { + case append + case rewrite +} diff --git a/PPG_framework/PPG_framework/README.md b/PPG_framework/PPG_framework/README.md index 1439703..bc5326a 100644 --- a/PPG_framework/PPG_framework/README.md +++ b/PPG_framework/PPG_framework/README.md @@ -2,62 +2,85 @@ ### [ Create certificate and upload it ] -### [ install framework (cocoapods?, direct download?) ] +### [ install framework (cocoapods or direct download), remember to add pod to extension target ] ### Add required code to AppDelegate -Open AppDelegate.swift file and creplace it's code to: +Open AppDelegate.swift file +Add required imports -``` -import UIKit +```swift import UserNotifications import PPG_framework +``` -@UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. - - // Initialize PPG framework - PPG.initNotifications("", application, handler: { result in - switch result { - case .error(let error): - // handle error - print(error) - return - case .success: - return - } - }) - - let center = UNUserNotificationCenter.current() - center.delegate = self - - return true - } +Add initialization code to given functions + +```swift +func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + + // Initialize PPG framework + PPG.initializeNotifications(projectId: "", apiToken: "") - func application(_ application: UIApplication, - didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - PPG.sendDeviceToken(deviceToken) { _ in } - } + // Register for push notifications if you do not already + PPG.registerForNotifications(application: application, handler: { result in + switch result { + case .error(let error): + // handle error + print(error) + return + case .success: + return + } + }) - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, - withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - - // Send information about received notification to framework - PPG.notificationReceived(notification: notification) { _ in } - - // Display notification when app is in foreground, optional - completionHandler([.alert, .badge, .sound]) - } + UNUserNotificationCenter.current().delegate = self - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + return true +} +``` - // Send information about clicked notification to framework - PPG.notificationClicked(response: response) { _ in } - } +``` +func applicationDidBecomeActive(_ application: UIApplication) { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. + + PPG.sendEventsDataToApi() +} +``` + +``` +func application(_ application: UIApplication, + didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + PPG.sendDeviceToken(deviceToken) { _ in } +} +``` + +```swift +func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + + // Display notification when app is in foreground, optional + completionHandler([.alert, .badge, .sound]) +} +``` + +```swift +func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + + // Send information about clicked notification to framework + PPG.notificationClicked(response: response) + + // Open external link from push notification + // Remove this section if this behavior is not expected + guard let url = PPG.getUrlFromNotificationResponse(response: response) + else { + completionHandler() + return + } + UIApplication.shared.open(url) + // + completionHandler() } ``` @@ -69,54 +92,87 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD 4. You can add capability by clicking on `+ Capability` button that is placed under `Signing & Capabilities` button. 5. Add `Background Modes` capability unless it is already on your capability list. Then select `Remote notifications`. 6. Add `Push notifications` capability unless it is already on your capability list. -7. Make sure that your `Provisioning Profile` has required capabilities. If you didn't add them while creating Provisioning Profile for your app you should go to your Apple Developer Center to add them then refresh your profile in Xcode project. +7. Make sure that your `Provisioning Profile` has required capabilities. If you didn't add them while creating Provisioning Profile for your app you should go to your Apple Developer Center to add them. Then refresh your profile in Xcode project. ### Create Notification Service Extension -This step is not required but it will allow application to display notifications with images. +This step is not required but it allows application to display notifications with images. -1. Open your Xcode project and click. -2. `File -> New -> Target`. +1. Open your Xcode project +2. Go to `File -> New -> Target`. 3. Select `Notification Service Extension`. 4. Choose a suitable name for it (for example `PPGNotificationServiceExtension`). 5. Open `NotificationService.swift` file. -6. Change `didReceive` function to: +6. Change `didReceive` function to: (use dispatch_group here to make sure that extension returns only when delivery event is sent and notification content is updated) -``` +```swift override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler - bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) - - if let bestAttemptContent = bestAttemptContent { - contentHandler(PPG.modifyNotification(bestAttemptContent)) + self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) + + guard let content = bestAttemptContent else { return } + + // Wait for delivery event result & image fetch before returning from extension + let group = DispatchGroup() + group.enter() + group.enter() + + PPG.notificationDelivered(notificationRequest: request) { _ in + group.leave() + } + + DispatchQueue.global().async { [weak self] in + self?.bestAttemptContent = PPG.modifyNotification(content) + group.leave() + } + + group.notify(queue: .main) { + contentHandler(self.bestAttemptContent ?? content) } } + ``` # Usage guide -### Unsubscribe user -`PPG.unsubscribeUser() { result in ... }` - ### Create and send Beacon -``` +```swift let beacon = Beacon() beacon.addSelector("Test_Selector", "0") + +// Methods with strategy and ttl support +// For append tag in concrete category (with ttl, default = 0) +beacon.appendTag("mytag", "mycategory") +beacon.appendTag("mytag", "mycategory", 3600) + +// For rewrite tag in concrete category (with ttl, default = 0) +beacon.rewriteTag("mytag", "mycategory") +beacon.rewriteTag("mytag", "mycategory", 3600) + +// For delete tag in concrete category (with ttl, default = 0) +beacon.deleteTag("mytag", "mycategory"); + +// Legacy methods (not supports strategy append/rewrite and ttl) beacon.addTag("new_tag", "new_tag_label") beacon.addTagToDelete(BeaconTag(tag: "my_old_tag", label: "my_old_tag_label")) + beacon.send() { result in } ``` +### Unsubscribe user +`PPG.unsubscribeUser { result in ... }` + ### Send Event -Event's purpose is telling API about newly received notifications. +Event's purpose is to tell API about newly received notifications. You should send events every time when user: -1. Receive notification -`PPG.notificationReceived(notification: notification) { _ in }` +1. Received notification in extension +`PPG.notificationDelivered(notificationRequest: UNNotificationRequest, handler: @escaping (_ result: ActionResult)` 2. Click on notification -`PPG.notificationClicked(response: response) { _ in }` +`PPG.notificationClicked(response: UNNotificationResponse)` + +2. Click button inside notification +`PPG.notificationButtonClicked(response: response, button: 1)` -3. Click button inside notification -`PPG.notificationButtonClicked(response: response, button: 1) { _ in }` Available values for `button` are `1` and `2` diff --git a/README.md b/README.md index b4f8eb0..bc5326a 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ Open AppDelegate.swift file Add required imports -``` +```swift import UserNotifications import PPG_framework ``` Add initialization code to given functions -``` +```swift func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. @@ -56,7 +56,7 @@ func application(_ application: UIApplication, } ``` -``` +```swift func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { @@ -65,7 +65,7 @@ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent noti } ``` -``` +```swift func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // Send information about clicked notification to framework @@ -105,7 +105,7 @@ This step is not required but it allows application to display notifications wit 5. Open `NotificationService.swift` file. 6. Change `didReceive` function to: (use dispatch_group here to make sure that extension returns only when delivery event is sent and notification content is updated) -``` +```swift override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.contentHandler = contentHandler self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) @@ -136,12 +136,27 @@ override func didReceive(_ request: UNNotificationRequest, withContentHandler co # Usage guide ### Create and send Beacon -``` +```swift let beacon = Beacon() -beacon.addSelector("Test_Selector", "value") +beacon.addSelector("Test_Selector", "0") + +// Methods with strategy and ttl support +// For append tag in concrete category (with ttl, default = 0) +beacon.appendTag("mytag", "mycategory") +beacon.appendTag("mytag", "mycategory", 3600) + +// For rewrite tag in concrete category (with ttl, default = 0) +beacon.rewriteTag("mytag", "mycategory") +beacon.rewriteTag("mytag", "mycategory", 3600) + +// For delete tag in concrete category (with ttl, default = 0) +beacon.deleteTag("mytag", "mycategory"); + +// Legacy methods (not supports strategy append/rewrite and ttl) beacon.addTag("new_tag", "new_tag_label") -beacon.addTagToDelete("tag_to_delete") -beacon.send { result in ... } +beacon.addTagToDelete(BeaconTag(tag: "my_old_tag", label: "my_old_tag_label")) + +beacon.send() { result in } ``` ### Unsubscribe user