-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathNotificationManager.swift
248 lines (246 loc) · 11.2 KB
/
NotificationManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//
// notifCenterDelegate.swift
// covid19-sounds
//
// Authors:
//
// Andreas Grammenos
//
// Copyright © 2020 Computer Lab / Mobile Systems Group. All rights reserved.
//
import Foundation
import Combine
import UserNotifications
/// The notification manager class which is used to handle, register, and schedule the notifications.
///
class NotificationManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
/// shared notification manager instance.
static let shared = NotificationManager()
#if DEBUG
/// Number of seconds between notifications in debug mode.
static let notificationIntervalSeconds: Double = 10 * 60
#else
/// Number of seconds between notifications.
static let notificationIntervalSeconds: Double = 3600 * 24 * 2
#endif
let objectWillChange = ObservableObjectPublisher()
/// Check if notifications are enabled - note that this property has to be explicitly posted on the main thread.
var notificationAuthorisation: UNAuthorizationStatus = .denied {
didSet { DispatchQueue.main.async { self.objectWillChange.send() } }
}
// do it as a singleton
private override init() { super.init() }
/// This function determines what to do when the user responds to a previously set notification
///
/// - Parameter center: the notification center instance of type `UNUserNotificationCenter`.
///
/// - Parameter response: the response of type `UNNotificationResponse`.
///
/// - Parameter completionHandler: the completion handler to run.
///
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
// How to respond to user navigation with notification
if response.actionIdentifier == UNNotificationDefaultActionIdentifier {
log.info("Open app from notification")
handleNotification()
} else if response.actionIdentifier == UNNotificationDismissActionIdentifier {
log.info("Notification Dismissed")
handleNotification()
} else {
log.error("Unknown notification action")
}
// invoke the completion handler
completionHandler()
}
/// This function is responsible to gauge if the user has authorised the use of notifications
///
public func areNotificationsEnabled() {
UNUserNotificationCenter
.current()
.getNotificationSettings(completionHandler: { settings in
self.notificationAuthorisation = settings.authorizationStatus
})
}
/// This function is responsible for registering the notifications that the user will have to see. We only
/// register the observer automatically if we are already authorized unless the `register` argument
/// is `true`, in which case we prompt the user to allow the notifications.
///
/// - Parameter register: a `Bool` flag that sets if we want to register the observer or not.
///
public func registerNotificationObserver(register: Bool = false) {
log.info("Attempting to register the notification observer.")
// grab the current notification center settings
UNUserNotificationCenter
.current()
.getNotificationSettings(completionHandler: { settings in
// set the notification status
DispatchQueue.main.async {
self.notificationAuthorisation = settings.authorizationStatus
if settings.authorizationStatus == .authorized {
log.info("Notifications are authorised")
self.requestObserverRegistration()
} else if settings.authorizationStatus == .notDetermined {
log.warning("User has not selected any notification status yet")
if register {
log.info("Register flag is up - registering")
self.requestObserverRegistration()
}
} else if settings.authorizationStatus == .denied {
log.warning("Notifications are not authorised")
self.notificationAuthorisation = .denied
} else {
log.warning("Provisional or ephemeral notification ")
self.requestObserverRegistration()
}
}
})
}
/// This function is responsible for registering the notification observer by request. If the notifications
/// are already allows it just registers the observer, however if the notifications are _not_ yet authorised
/// or denied the request is delegated to the end of the survey.
///
func requestObserverRegistration() {
// fetch current instance of the notification center
let notificatonCenter = UNUserNotificationCenter.current()
// assign the class to the notification center delegate
notificatonCenter.delegate = NotificationManager.shared
// register notification types and actions
let action = UNNotificationAction(identifier: UNNotificationDismissActionIdentifier,
title: "Dismiss",
options: [])
// setup the category - we only require one
let category = UNNotificationCategory(identifier: "dismissCategory",
actions: [action],
intentIdentifiers: [],
options: .customDismissAction)
// set the notification categories
notificatonCenter.setNotificationCategories([category])
//
// Request permission to display notifications
notificatonCenter
.requestAuthorization(options: [.alert, .sound],
completionHandler: { (granted, err) in
if granted {
log.info("Notification Authorisation probe was successful - status: \(granted)")
log.info("Notification observer registered successfully - setting nofications to be enabled.")
self.notificationAuthorisation = .authorized
} else {
log.error("Notification authorisation probe was unsuccessfull - reason: \(err.debugDescription)")
log.info("Setting nofications to be denied")
self.notificationAuthorisation = .denied
}
})
}
/// Set a notification every x days
///
/// - Parameter `interval`: the interval between the notifications in seconds.
///
/// - Parameter `completionHandler`: the completion handler which is called after successful execution.
///
func scheduleNotification(interval: Double = NotificationManager.notificationIntervalSeconds,
completionHandler: (() -> Void)? = nil) {
// Create the content of the notification
let content = createNotificationContent()
// Create the trigger condition for the notification
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: interval, repeats: true)
// Create the notification request
let request = UNNotificationRequest(identifier: "default", content: content, trigger: trigger)
// Ask the OS for the notification to be scheduled
registerNotification(request, handler: completionHandler)
}
/// Create message that will be dosplayed to the user upon notification
///
/// - Returns: returns the `UNMutableNotificationContent` which contains the notification content to display
///
func createNotificationContent() -> UNMutableNotificationContent {
let content = UNMutableNotificationContent()
content.title = "How are you feeling?".localized()
content.body = "Take a moment to complete a brief assessment".localized()
content.sound = UNNotificationSound.default
content.categoryIdentifier = "dismissCategory"
return content
}
/// Request the OS for a notification to be scheduled
///
/// - Parameter notificationReqquest: the `UNNotificationRequest` to be registered,
///
/// - Parameter handler: the handler to call once the notification is fired.
///
func registerNotification(_ notificationRequest: UNNotificationRequest,
handler: (() -> Void)? = nil) {
let notifCenter = UNUserNotificationCenter.current()
notifCenter.add(notificationRequest) { (error) in
if error != nil {
// Handle any errors.
log.error("Unable to Schedule Notification: \(String(describing: error))")
} else {
log.info("Notification registered using notification interval of: " +
"\(NotificationManager.notificationIntervalSeconds) seconds.")
(handler ?? {
log.error("No registerNotification Handler Invoked")
}) ()
}
}
}
/// Check if scheduled notifications are the same duration as notificationIntervalDays
/// If not, resets them
///
/// - Parameter handler: the optional handler to call if the notification needs to be scheduled.
///
func checkNotificationSettings(handler: (() -> Void)? = nil) {
UNUserNotificationCenter
.current()
.getPendingNotificationRequests(completionHandler: { requests in
// check if the notification requests are empty
if requests.isEmpty {
log.info("No notifications set")
} else if requests.count == 1 {
// Need to force convert the trigger check time
guard let trigger = requests[0].trigger as? UNTimeIntervalNotificationTrigger else {
log.error("Could not cast the request object to UNTimeIntervalNotificationTrigger - cannot proceed.")
return
}
//
if trigger.timeInterval == NotificationManager.notificationIntervalSeconds {
log.info("Notification interval of \(trigger.timeInterval) seconds is same as " +
"notificationIntervalSeconds which is: \(NotificationManager.notificationIntervalSeconds) ")
} else {
log.warning("Notification interval of \(trigger.timeInterval) is different to " +
"notificationIntervalSeconds which is " +
"\(NotificationManager.notificationIntervalSeconds) - updating its value",
context: {
// Notifications previously set, reset with new interval
if appStatus.notificationsSet {
self.scheduleNotification(interval: NotificationManager.notificationIntervalSeconds,
completionHandler: handler)
} else {
// Something went wrong
log.error("Invalid notification state")
}
})
}
//
} else {
// handle the case of too many registered notification - in this case we remove all and re-register them.
log.warning("Too many notifications, removing all and scheduling new one")
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
if appStatus.notificationsSet {
self.scheduleNotification(interval: NotificationManager.notificationIntervalSeconds,
completionHandler: {})
} else {
log.error("Invalid notification state")
}
}
})
}
/// Check whether notifications are scheduled when user interacts with a previous notification and if not, sets it
///
/// Currently not implemented,
///
func handleNotification() {
log.info("Notification handler invoked.")
return
}
}