More user interface for push notifications

This commit is contained in:
Jackson Harper
2022-12-09 18:07:40 +08:00
parent 19f02a8cea
commit 0ae690f13f
5 changed files with 82 additions and 20 deletions

View File

@ -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 {

View File

@ -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<MutationResult, Unions.SetDeviceTokenResult> {
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))
}
}
}
}
}

View File

@ -25,4 +25,5 @@ public enum UserDefaultKey: String {
case themeName
case shouldShowNewFeaturePrimer
case notificationsEnabled
case deviceTokenID
}

View File

@ -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
}

View File

@ -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))
}
}
}