[ios] Add notification counter badge to app icon

Add a persistent counter that is incremented from notification
extension. The counter is reset on app being put into foreground.

Close #1757
This commit is contained in:
ivk 2025-10-06 12:19:04 +02:00
parent b1300345d4
commit 9100edc7b8
5 changed files with 33 additions and 4 deletions

View file

@ -6,6 +6,8 @@ import tutasdk
var suspensionEndTime: Date?
class NotificationService: UNNotificationServiceExtension {
private let notificationStorage = NotificationStorage(userPreferencesProvider: UserPreferencesProviderImpl())
private var contentHandler: ((UNNotificationContent) -> Void)?
private var bestAttemptContent: UNMutableNotificationContent?
private let urlSession: URLSession = makeUrlSession()
@ -13,8 +15,13 @@ class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
notificationStorage.incrementNotificationCount()
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent {
bestAttemptContent.badge = notificationStorage.notificationCount as NSNumber
Task {
await notificationHandleTask(bestAttemptContent)
contentHandler(bestAttemptContent)
@ -47,7 +54,6 @@ class NotificationService: UNNotificationServiceExtension {
let keychainManager = KeychainManager(keyGenerator: KeyGenerator())
let keychainEncryption = KeychainEncryption(keychainManager: keychainManager)
let credentialsFacade = IosNativeCredentialsFacade(keychainEncryption: keychainEncryption, credentialsDb: credentialsDb, cryptoFns: CryptoFunctions())
let notificationStorage = NotificationStorage(userPreferencesProvider: UserPreferencesProviderImpl())
let mailId = content.userInfo["mailId"] as? [String]
let userId = content.userInfo["userId"] as? String

View file

@ -7,6 +7,7 @@ private let LAST_PROCESSED_NOTIFICAION_ID_KEY = "lastProcessedNotificationId"
private let LAST_MISSED_NOTIFICATION_CHECK_TIME = "lastMissedNotificationCheckTime"
private let EXTENDED_NOTIFICATION_MODE = "extendedNotificationMode"
private let RECEIVE_CALENDAR_NOTIFICATION_CONFIG = "receiveCalendarNotificationConfig"
private let NOTIFICAITON_COUNT_KEY = "notificationCount"
public class NotificationStorage {
private let userPreferencesProvider: UserPreferencesProvider
@ -79,6 +80,13 @@ public class NotificationStorage {
set { return self.userPreferencesProvider.setValue(newValue, forKey: LAST_MISSED_NOTIFICATION_CHECK_TIME) }
}
/// Count of email notifications received but not viewed
public var notificationCount: Int { get { self.userPreferencesProvider.getObject(forKey: NOTIFICAITON_COUNT_KEY) as! Int? ?? 0 } }
public func incrementNotificationCount() { self.userPreferencesProvider.setValue(self.notificationCount + 1, forKey: NOTIFICAITON_COUNT_KEY) }
public func resetNotificaitonCount() { self.userPreferencesProvider.setValue(0, forKey: NOTIFICAITON_COUNT_KEY) }
public func clear() {
TUTSLog("UserPreference clear")
let sseInfo = self.sseInfo

View file

@ -28,6 +28,14 @@ public class SdkRestClient: RestClient {
private func mapExceptionToError(e: Error) -> RestClientError {
// why we don't match on e? see: sdkFileClient::mapExceptionToError
TUTSLog("Exception in SdkRestClient: \(e). Assuming .Unknown")
if let e = e as? URLError {
switch e.code {
case .notConnectedToInternet, .timedOut, .cannotFindHost, .networkConnectionLost, URLError.Code.notConnectedToInternet,
URLError.Code.dnsLookupFailed:
return .NetworkError
default: break
}
}
return RestClientError.Unknown
}
}

View file

@ -13,6 +13,8 @@ public let TUTA_CALENDAR_INTEROP_SCHEME = "tutacalendar"
private var viewController: ViewController!
private let urlSession: URLSession = makeUrlSession()
private var notificationStorage: NotificationStorage!
func registerForPushNotifications() async throws -> String {
#if targetEnvironment(simulator)
return ""
@ -37,7 +39,7 @@ public let TUTA_CALENDAR_INTEROP_SCHEME = "tutacalendar"
spawnTransactionFinisher()
let userPreferencesProvider = UserPreferencesProviderImpl()
let notificationStorage = NotificationStorage(userPreferencesProvider: userPreferencesProvider)
self.notificationStorage = NotificationStorage(userPreferencesProvider: userPreferencesProvider)
let keychainManager = KeychainManager(keyGenerator: KeyGenerator())
let keychainEncryption = KeychainEncryption(keychainManager: keychainManager)
let dateProvider = SystemDateProvider()

View file

@ -16,6 +16,8 @@ public let MAILTO_SCHEME = "mailto"
private var viewController: ViewController!
private let urlSession: URLSession = makeUrlSession()
private var notificationStorage: NotificationStorage!
@MainActor func registerForPushNotifications() async throws -> String {
#if targetEnvironment(simulator)
return ""
@ -32,7 +34,7 @@ public let MAILTO_SCHEME = "mailto"
spawnTransactionFinisher()
let userPreferencesProvider = UserPreferencesProviderImpl()
let notificationStorage = NotificationStorage(userPreferencesProvider: userPreferencesProvider)
self.notificationStorage = NotificationStorage(userPreferencesProvider: userPreferencesProvider)
let keychainManager = KeychainManager(keyGenerator: KeyGenerator())
let keychainEncryption = KeychainEncryption(keychainManager: keychainManager)
let dateProvider: SystemDateProvider = SystemDateProvider()
@ -92,7 +94,10 @@ public let MAILTO_SCHEME = "mailto"
return true
}
func applicationWillEnterForeground(_ application: UIApplication) { UIApplication.shared.applicationIconBadgeNumber = 0 }
func applicationWillEnterForeground(_ application: UIApplication) {
UIApplication.shared.applicationIconBadgeNumber = 0
self.notificationStorage.resetNotificaitonCount()
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
let stringToken = deviceTokenAsString(deviceToken: deviceToken)