-
Notifications
You must be signed in to change notification settings - Fork 5
refactor: notification service extension [1] #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: rc/5.7.0
Are you sure you want to change the base?
Changes from all commits
dba2c2c
04f8fe5
ae3daca
e6ea51b
f8088bf
20a8284
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,92 +1,55 @@ | ||
| // Copyright © 2022 Optimove. All rights reserved. | ||
|
|
||
| import Foundation | ||
| import UserNotifications | ||
|
|
||
| let MAX_DYNAMIC_CATEGORIES = 128 | ||
| let DYNAMIC_CATEGORY_IDENTIFIER = "__kumulos_category_%d__" | ||
|
|
||
| @available(iOS 10.0, *) | ||
| class CategoryManager { | ||
| let categoryReadLock = DispatchSemaphore(value: 0) | ||
| let dynamicCategoryLock = DispatchSemaphore(value: 1) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These locks were replaced with the Swift async. You will find the usage below. |
||
|
|
||
| fileprivate static var instance: CategoryManager? | ||
|
|
||
| static var sharedInstance: CategoryManager { | ||
| if instance == nil { | ||
| instance = CategoryManager() | ||
| } | ||
|
|
||
| return instance! | ||
| enum CategoryManager { | ||
| enum Constants { | ||
| static let MAX_DYNAMIC_CATEGORIES = 128 | ||
| static let DYNAMIC_CATEGORY = OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue | ||
| } | ||
|
|
||
| static func getCategoryIdForMessageId(messageId: Int) -> String { | ||
| return String(format: DYNAMIC_CATEGORY_IDENTIFIER, messageId) | ||
| static func getCategoryId(messageId: Int) -> String { | ||
| return "__kumulos_category_\(messageId)__" | ||
| } | ||
|
|
||
| static func registerCategory(category: UNNotificationCategory) { | ||
| var categorySet = sharedInstance.getExistingCategories() | ||
| var storedDynamicCategories = sharedInstance.getExistingDynamicCategoriesList() | ||
| static func registerCategory(_ category: UNNotificationCategory) async { | ||
| var systemCategories = await UNUserNotificationCenter.current().notificationCategories() | ||
| var storedCategoryIds = readCategoryIds() | ||
|
|
||
| systemCategories.insert(category) | ||
| storedCategoryIds.insert(category.identifier) | ||
|
|
||
| categorySet.insert(category) | ||
| storedDynamicCategories.append(category.identifier) | ||
| let (categories, categoryIds) = maybePruneCategories(categories: systemCategories, categoryIds: storedCategoryIds) | ||
|
|
||
| sharedInstance.pruneCategoriesAndSave(categories: categorySet, dynamicCategories: storedDynamicCategories) | ||
| UNUserNotificationCenter.current().setNotificationCategories(categories) | ||
| writeCategoryIds(categoryIds) | ||
|
|
||
| // Force a reload of the categories | ||
| _ = sharedInstance.getExistingCategories() | ||
| await UNUserNotificationCenter.current().notificationCategories() | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Awaiting for a result instead of manual mutex signaling |
||
| } | ||
|
|
||
| private func getExistingCategories() -> Set<UNNotificationCategory> { | ||
| var returnedCategories = Set<UNNotificationCategory>() | ||
|
|
||
| UNUserNotificationCenter.current().getNotificationCategories { (categories: Set<UNNotificationCategory>) in | ||
| returnedCategories = Set<UNNotificationCategory>(categories) | ||
|
|
||
| self.categoryReadLock.signal() | ||
| } | ||
|
|
||
| _ = categoryReadLock.wait(timeout: DispatchTime.now() + DispatchTimeInterval.seconds(5)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not certain about deleting this. |
||
|
|
||
| return returnedCategories | ||
| static func readCategoryIds() -> Set<String> { | ||
| let array = UserDefaults.standard.object(forKey: Constants.DYNAMIC_CATEGORY) as? [String] ?? [] | ||
| return Set(array) | ||
| } | ||
|
|
||
| private func getExistingDynamicCategoriesList() -> [String] { | ||
| dynamicCategoryLock.wait() | ||
| defer { | ||
| dynamicCategoryLock.signal() | ||
| } | ||
|
|
||
| if let existingArray = UserDefaults.standard.object(forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) { | ||
| return existingArray as! [String] | ||
| } | ||
|
|
||
| let newArray = [String]() | ||
|
|
||
| UserDefaults.standard.set(newArray, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) | ||
|
|
||
| return newArray | ||
| static func writeCategoryIds(_ ids: Set<String>) { | ||
| UserDefaults.standard.set(Array(ids), forKey: Constants.DYNAMIC_CATEGORY) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The standard iOS persistence will be replaced with the storage abstraction in this bulk of PRs |
||
| } | ||
|
|
||
| private func pruneCategoriesAndSave(categories: Set<UNNotificationCategory>, dynamicCategories: [String]) { | ||
| if dynamicCategories.count <= MAX_DYNAMIC_CATEGORIES { | ||
| UNUserNotificationCenter.current().setNotificationCategories(categories) | ||
| UserDefaults.standard.set(dynamicCategories, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) | ||
| return | ||
| static func maybePruneCategories( | ||
| categories: Set<UNNotificationCategory>, | ||
| categoryIds: Set<String>, | ||
| limit: Int = Constants.MAX_DYNAMIC_CATEGORIES | ||
| ) -> (categories: Set<UNNotificationCategory>, categoryIds: Set<String>) { | ||
| if categoryIds.count <= limit, categories.count <= limit { | ||
| return (categories: categories, categoryIds: categoryIds) | ||
| } | ||
|
|
||
| let categoriesToRemove = dynamicCategories.prefix(dynamicCategories.count - MAX_DYNAMIC_CATEGORIES) | ||
|
|
||
| let prunedCategories = categories.filter { category -> Bool in | ||
| categoriesToRemove.firstIndex(of: category.identifier) == nil | ||
| } | ||
|
|
||
| let prunedDynamicCategories = dynamicCategories.filter { cat -> Bool in | ||
| categoriesToRemove.firstIndex(of: cat) == nil | ||
| } | ||
| let categoriesToRemove = categoryIds.prefix(categoryIds.count - limit) | ||
| let prunedCategories = categories.filter { !categoriesToRemove.contains($0.identifier) } | ||
| let prunedCategoryIds = categoryIds.subtracting(categoriesToRemove) | ||
|
|
||
| UNUserNotificationCenter.current().setNotificationCategories(prunedCategories) | ||
| UserDefaults.standard.set(prunedDynamicCategories, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) | ||
| return (categories: prunedCategories, categoryIds: prunedCategoryIds) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This URL was used here as a fallback URL, in case if no access to the persistent key-value storage.
Now, this code has the storage as a dependency in class init, and also will not be executed in the runtime.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be helpful to replace the notification content with the text "Error: Check Optimove AppGroup capabilities" if there is no storage available? This could help us reduce the number of integration issues related to missing images.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After some playground, I found that optimobile Core data throwing an no db file.