From 0ae690f13f67606adc6f78b392a148399ef4d856 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 9 Dec 2022 18:07:40 +0800 Subject: [PATCH] More user interface for push notifications --- .../Profile/PushNotificationSettings.swift | 25 ++++++++---- .../DataService/Mutations/DeviceToken.swift | 38 +++++++++++++++++-- .../Sources/Utils/UserDefaultKeys.swift | 1 + apple/Sources/AppDelegate.swift | 10 ++++- apple/Sources/PushNotificationConfig.swift | 28 ++++++++++---- 5 files changed, 82 insertions(+), 20 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettings.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettings.swift index db9bddddb..210974b0e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettings.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/PushNotificationSettings.swift @@ -1,3 +1,4 @@ + import Models import Services import SwiftUI @@ -18,19 +19,27 @@ import Views } } - func tryUpdateToDesired() { - print("trying to update to desired state: ", desiredNotificationsEnabled) + func tryUpdateToDesired(dataService: DataService) { + UserDefaults.standard.set(desiredNotificationsEnabled, forKey: UserDefaultKey.notificationsEnabled.rawValue) + if desiredNotificationsEnabled { - UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, error in - print("notification status: ", granted, "error: ", error) + UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in DispatchQueue.main.async { self.desiredNotificationsEnabled = granted + Task { + if let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) { + try? await dataService.syncDeviceToken(deviceTokenOperation: DeviceTokenOperation.addToken(token: savedToken)) + } + NotificationCenter.default.post(name: Notification.Name("ReconfigurePushNotifications"), object: nil) + } } } } else { - // UNUserNotificationCenter.current().r - UIApplication.shared.openURL(URL(string:"prefs:root=NOTIFICATIONS_ID")!) - + if let tokenID = UserDefaults.standard.string(forKey: UserDefaultKey.deviceTokenID.rawValue) { + Task { + try? await Services().dataService.syncDeviceToken(deviceTokenOperation: .deleteToken(tokenID: tokenID)) + } + } } } } @@ -61,7 +70,7 @@ struct PushNotificationSettingsView: View { Section { Toggle(isOn: $viewModel.desiredNotificationsEnabled, label: { Text("Notifications Enabled") }) }.onChange(of: viewModel.desiredNotificationsEnabled) { _ in - viewModel.tryUpdateToDesired() + viewModel.tryUpdateToDesired(dataService: dataService) } Section { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeviceToken.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeviceToken.swift index ec5692bdd..cf2a17ded 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeviceToken.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeviceToken.swift @@ -1,6 +1,7 @@ import Foundation import Models import SwiftGraphQL +import Utils public enum DeviceTokenOperation { case addToken(token: String) @@ -26,17 +27,22 @@ public enum DeviceTokenOperation { } public extension DataService { - func syncDeviceToken(deviceTokenOperation: DeviceTokenOperation) { + func syncDeviceToken(deviceTokenOperation: DeviceTokenOperation) async throws -> String? { enum MutationResult { - case saved(id: String) + case saved(id: String, token: String?) case error(errorCode: Enums.SetDeviceTokenErrorCode) } + let success = Selection.DeviceToken { (id: try $0.id(), token: try $0.token()) } + let selection = Selection { try $0.on( setDeviceTokenError: .init { .error(errorCode: try $0.errorCodes().first ?? .badRequest) }, setDeviceTokenSuccess: .init { - .saved(id: try $0.deviceToken(selection: Selection.DeviceToken { try $0.id() })) + .saved( + id: try $0.deviceToken(selection: success).id, + token: try $0.deviceToken(selection: success).token + ) } ) } @@ -54,6 +60,30 @@ public extension DataService { let path = appEnvironment.graphqlPath let headers = networker.defaultHeaders - send(mutation, to: path, headers: headers) { _ in } + return try await withCheckedThrowingContinuation { continuation in + send(mutation, to: path, headers: headers) { result in + guard let payload = try? result.get() else { + continuation.resume(throwing: BasicError.message(messageText: "network error")) + return + } + + switch payload.data { + case let .saved(id: id, token: token): + switch deviceTokenOperation { + case .deleteToken(tokenID: _): + // When we delete we don't remove the saved token, as we might need to re-use it + // in the future + UserDefaults.standard.removeObject(forKey: UserDefaultKey.deviceTokenID.rawValue) + case .addToken(token: _): + UserDefaults.standard.set(id, forKey: UserDefaultKey.deviceTokenID.rawValue) + UserDefaults.standard.set(token, forKey: UserDefaultKey.firebasePushToken.rawValue) + } + + continuation.resume(returning: id) + case let .error(errorCode: errorCode): + continuation.resume(throwing: BasicError.message(messageText: errorCode.rawValue)) + } + } + } } } diff --git a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift index d0b032f3a..a5ce0ec7f 100644 --- a/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift +++ b/apple/OmnivoreKit/Sources/Utils/UserDefaultKeys.swift @@ -25,4 +25,5 @@ public enum UserDefaultKey: String { case themeName case shouldShowNewFeaturePrimer case notificationsEnabled + case deviceTokenID } diff --git a/apple/Sources/AppDelegate.swift b/apple/Sources/AppDelegate.swift index 58911808b..45b292604 100644 --- a/apple/Sources/AppDelegate.swift +++ b/apple/Sources/AppDelegate.swift @@ -46,7 +46,15 @@ private let logger = Logger(subsystem: "app.omnivore", category: "app-delegate") } Services.registerBackgroundFetch() - configurePushNotifications() + configureFirebase() + + NotificationCenter.default.addObserver(forName: Notification.Name("ReconfigurePushNotifications"), object: nil, queue: OperationQueue.main) { _ in + if UserDefaults.standard.bool(forKey: UserDefaultKey.notificationsEnabled.rawValue) { + self.registerForNotifications() + } else { + self.unregisterForNotifications() + } + } return true } diff --git a/apple/Sources/PushNotificationConfig.swift b/apple/Sources/PushNotificationConfig.swift index da9fa33a3..faa27975c 100644 --- a/apple/Sources/PushNotificationConfig.swift +++ b/apple/Sources/PushNotificationConfig.swift @@ -8,9 +8,7 @@ import UIKit import Utils extension AppDelegate { - func configurePushNotifications() { - guard FeatureFlag.enablePushNotifications else { return } - + func configureFirebase() { let keys: FirebaseKeys? = { let isProd = (PublicValet.storedAppEnvironment ?? .initialAppEnvironment) == .prod let firebaseKeys = isProd ? AppKeys.sharedInstance?.firebaseProdKeys : AppKeys.sharedInstance?.firebaseDemoKeys @@ -30,9 +28,21 @@ extension AppDelegate { FirebaseApp.configure(options: firebaseOpts) FirebaseConfiguration.shared.setLoggerLevel(.min) + if UserDefaults.standard.bool(forKey: UserDefaultKey.notificationsEnabled.rawValue) { + registerForNotifications() + } + } + + func registerForNotifications() { + Messaging.messaging().delegate = self UNUserNotificationCenter.current().delegate = self UIApplication.shared.registerForRemoteNotifications() - Messaging.messaging().delegate = self + } + + func unregisterForNotifications() { + Messaging.messaging().delegate = nil + UNUserNotificationCenter.current().delegate = nil + UIApplication.shared.unregisterForRemoteNotifications() } } @@ -87,12 +97,16 @@ extension AppDelegate: MessagingDelegate { guard let fcmToken = fcmToken else { return } let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) + let deviceTokenID = UserDefaults.standard.string(forKey: UserDefaultKey.deviceTokenID.rawValue) - if savedToken == fcmToken { + // If the deviceTokenID is null, that means we haven't set our token yet, and this is just + // a previously saved token. + if savedToken == fcmToken, deviceTokenID != nil { return } - UserDefaults.standard.set(fcmToken, forKey: UserDefaultKey.firebasePushToken.rawValue) - Services().dataService.syncDeviceToken(deviceTokenOperation: .addToken(token: fcmToken)) + Task { + try? await Services().dataService.syncDeviceToken(deviceTokenOperation: .addToken(token: fcmToken)) + } } }