Skip to content

Commit 02cf86b

Browse files
authored
Merge pull request #198 from loudnate/ios10
iOS 10
2 parents 2265776 + 5806ace commit 02cf86b

24 files changed

+515
-478
lines changed

Loop.xcodeproj/project.pbxproj

Lines changed: 25 additions & 18 deletions
Large diffs are not rendered by default.

Loop/AppDelegate.swift

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import UIKit
10+
import UserNotifications
1011
import CarbKit
1112
import InsulinKit
1213

@@ -20,7 +21,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
2021
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
2122
window?.tintColor = UIColor.tintColor
2223

23-
NotificationManager.authorize()
24+
NotificationManager.authorize(delegate: self)
2425

2526
AnalyticsManager.sharedManager.application(application, didFinishLaunchingWithOptions: launchOptions)
2627

@@ -60,27 +61,25 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
6061

6162
}
6263

63-
// MARK: - Notifications
64+
// MARK: - 3D Touch
6465

65-
func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
66-
if application.applicationState == .active {
67-
if let message = notification.alertBody {
68-
window?.rootViewController?.presentAlertController(withTitle: notification.alertTitle, message: message, animated: true, completion: nil)
69-
}
70-
}
66+
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
67+
completionHandler(false)
7168
}
69+
}
7270

73-
func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, withResponseInfo responseInfo: [AnyHashable: Any], completionHandler: @escaping () -> Void) {
7471

75-
switch identifier {
76-
case NotificationManager.Action.RetryBolus.rawValue?:
77-
if let units = notification.userInfo?[NotificationManager.UserInfoKey.BolusAmount.rawValue] as? Double,
78-
let startDate = notification.userInfo?[NotificationManager.UserInfoKey.BolusStartDate.rawValue] as? Date,
72+
extension AppDelegate: UNUserNotificationCenterDelegate {
73+
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
74+
switch response.actionIdentifier {
75+
case NotificationManager.Action.RetryBolus.rawValue:
76+
if let units = response.notification.request.content.userInfo[NotificationManager.UserInfoKey.BolusAmount.rawValue] as? Double,
77+
let startDate = response.notification.request.content.userInfo[NotificationManager.UserInfoKey.BolusStartDate.rawValue] as? Date,
7978
startDate.timeIntervalSinceNow >= TimeInterval(minutes: -5)
8079
{
8180
AnalyticsManager.sharedManager.didRetryBolus()
8281

83-
dataManager.enactBolus(units) { (error) in
82+
dataManager.enactBolus(units: units) { (error) in
8483
if error != nil {
8584
NotificationManager.sendBolusFailureNotificationForAmount(units, atStartDate: startDate)
8685
}
@@ -92,13 +91,11 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
9291
default:
9392
break
9493
}
95-
94+
9695
completionHandler()
9796
}
9897

99-
// MARK: - 3D Touch
100-
101-
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
102-
completionHandler(false)
98+
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
99+
completionHandler([.badge, .sound, .alert])
103100
}
104101
}
File renamed without changes.

Loop/Extensions/NSData.swift

Lines changed: 0 additions & 30 deletions
This file was deleted.

Loop/Managers/DeviceDataManager.swift

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ final class DeviceDataManager: CarbStoreDelegate, DoseStoreDelegate, Transmitter
114114
AnalyticsManager.sharedManager.didChangeRileyLinkConnectionState()
115115

116116
if connectedPeripheralIDs.count == 0 {
117-
NotificationManager.clearLoopNotRunningNotifications()
117+
NotificationManager.clearPendingNotificationRequests()
118118
}
119119
}
120120

@@ -360,13 +360,12 @@ final class DeviceDataManager: CarbStoreDelegate, DoseStoreDelegate, Transmitter
360360
}
361361
}
362362

363-
/**
364-
Send a bolus command and handle the result
365-
366-
- parameter completion: A closure called after the command is complete. This closure takes a single argument:
367-
- error: An error describing why the command failed
368-
*/
369-
func enactBolus(_ units: Double, completion: @escaping (_ error: Error?) -> Void) {
363+
/// Send a bolus command and handle the result
364+
///
365+
/// - parameter units: The number of units to deliver
366+
/// - parameter completion: A clsure called after the command is complete. This closure takes a single argument:
367+
/// - error: An error describing why the command failed
368+
func enactBolus(units: Double, completion: @escaping (_ error: Error?) -> Void) {
370369
guard units > 0 else {
371370
completion(nil)
372371
return
@@ -442,9 +441,7 @@ final class DeviceDataManager: CarbStoreDelegate, DoseStoreDelegate, Transmitter
442441
}
443442

444443
// MARK: - G5 Transmitter
445-
/**
446-
The G5 transmitter is a reliable heartbeat by which we can assert the loop state.
447-
*/
444+
/// The G5 transmitter is a reliable heartbeat by which we can assert the loop state.
448445

449446
// MARK: TransmitterDelegate
450447

Loop/Managers/NotificationManager.swift

Lines changed: 93 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
import UIKit
10+
import UserNotifications
1011

1112

1213
struct NotificationManager {
@@ -27,74 +28,72 @@ struct NotificationManager {
2728
case BolusStartDate
2829
}
2930

30-
static var userNotificationSettings: UIUserNotificationSettings {
31-
let retryBolusAction = UIMutableUserNotificationAction()
32-
retryBolusAction.title = NSLocalizedString("Retry", comment: "The title of the notification action to retry a bolus command")
33-
retryBolusAction.identifier = Action.RetryBolus.rawValue
34-
retryBolusAction.activationMode = .background
35-
36-
let bolusFailureCategory = UIMutableUserNotificationCategory()
37-
bolusFailureCategory.identifier = Category.BolusFailure.rawValue
38-
bolusFailureCategory.setActions([
39-
retryBolusAction
40-
],
41-
for: .default
42-
)
31+
private static var notificationCategories: Set<UNNotificationCategory> {
32+
var categories = [UNNotificationCategory]()
4333

44-
return UIUserNotificationSettings(
45-
types: [.badge, .sound, .alert],
46-
categories: [
47-
bolusFailureCategory
48-
]
34+
let retryBolusAction = UNNotificationAction(
35+
identifier: Action.RetryBolus.rawValue,
36+
title: NSLocalizedString("Retry", comment: "The title of the notification action to retry a bolus command"),
37+
options: []
4938
)
39+
40+
categories.append(UNNotificationCategory(
41+
identifier: Category.BolusFailure.rawValue,
42+
actions: [retryBolusAction],
43+
intentIdentifiers: [],
44+
options: []
45+
))
46+
47+
return Set(categories)
5048
}
5149

52-
static func authorize() {
53-
UIApplication.shared.registerUserNotificationSettings(userNotificationSettings)
50+
static func authorize(delegate: UNUserNotificationCenterDelegate) {
51+
let center = UNUserNotificationCenter.current()
52+
53+
center.delegate = delegate
54+
center.requestAuthorization(options: [.badge, .sound, .alert], completionHandler: { _, _ in })
55+
center.setNotificationCategories(notificationCategories)
5456
}
5557

5658
// MARK: - Notifications
5759

5860
static func sendBolusFailureNotificationForAmount(_ units: Double, atStartDate startDate: Date) {
59-
let notification = UILocalNotification()
61+
let notification = UNMutableNotificationContent()
6062

61-
notification.alertTitle = NSLocalizedString("Bolus", comment: "The notification title for a bolus failure")
62-
notification.alertBody = String(format: NSLocalizedString("%@ U bolus may have failed.", comment: "The notification alert describing a possible bolus failure. The substitution parameter is the size of the bolus in units."), NumberFormatter.localizedString(from: NSNumber(value: units), number: .decimal))
63-
notification.soundName = UILocalNotificationDefaultSoundName
63+
notification.title = NSLocalizedString("Bolus", comment: "The notification title for a bolus failure")
64+
notification.body = String(format: NSLocalizedString("%@ U bolus may have failed.", comment: "The notification alert describing a possible bolus failure. The substitution parameter is the size of the bolus in units."), NumberFormatter.localizedString(from: NSNumber(value: units), number: .decimal))
65+
notification.sound = UNNotificationSound.default()
6466

6567
if startDate.timeIntervalSinceNow >= TimeInterval(minutes: -5) {
66-
notification.category = Category.BolusFailure.rawValue
68+
notification.categoryIdentifier = Category.BolusFailure.rawValue
6769
}
6870

6971
notification.userInfo = [
7072
UserInfoKey.BolusAmount.rawValue: units,
7173
UserInfoKey.BolusStartDate.rawValue: startDate
7274
]
7375

74-
UIApplication.shared.presentLocalNotificationNow(notification)
76+
let request = UNNotificationRequest(
77+
// Only support 1 bolus notification at once
78+
identifier: Category.BolusFailure.rawValue,
79+
content: notification,
80+
trigger: nil
81+
)
82+
83+
UNUserNotificationCenter.current().add(request)
7584
}
7685

7786
// Cancel any previous scheduled notifications in the Loop Not Running category
78-
static func clearLoopNotRunningNotifications() {
79-
let app = UIApplication.shared
80-
81-
app.scheduledLocalNotifications?.filter({
82-
$0.category == Category.LoopNotRunning.rawValue
83-
}).forEach({
84-
app.cancelLocalNotification($0)
85-
})
87+
static func clearPendingNotificationRequests() {
88+
UNUserNotificationCenter.current().removeAllPendingNotificationRequests()
8689
}
8790

8891
static func scheduleLoopNotRunningNotifications() {
89-
let app = UIApplication.shared
90-
91-
clearLoopNotRunningNotifications()
92-
9392
// Give a little extra time for a loop-in-progress to complete
9493
let gracePeriod = TimeInterval(minutes: 0.5)
9594

9695
for minutes: Double in [20, 40, 60, 120] {
97-
let notification = UILocalNotification()
96+
let notification = UNMutableNotificationContent()
9897
let failureInterval = TimeInterval(minutes: minutes)
9998

10099
let formatter = DateComponentsFormatter()
@@ -103,46 +102,66 @@ struct NotificationManager {
103102
formatter.unitsStyle = .full
104103

105104
if let failueIntervalString = formatter.string(from: failureInterval)?.localizedLowercase {
106-
notification.alertBody = String(format: NSLocalizedString("Loop has not completed successfully in %@", comment: "The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop"), failueIntervalString)
105+
notification.body = String(format: NSLocalizedString("Loop has not completed successfully in %@", comment: "The notification alert describing a long-lasting loop failure. The substitution parameter is the time interval since the last loop"), failueIntervalString)
107106
}
108107

109-
notification.alertTitle = NSLocalizedString("Loop Failure", comment: "The notification title for a loop failure")
110-
notification.fireDate = Date(timeIntervalSinceNow: failureInterval + gracePeriod)
111-
notification.soundName = UILocalNotificationDefaultSoundName
112-
notification.category = Category.LoopNotRunning.rawValue
113-
114-
app.scheduleLocalNotification(notification)
108+
notification.title = NSLocalizedString("Loop Failure", comment: "The notification title for a loop failure")
109+
notification.sound = UNNotificationSound.default()
110+
notification.categoryIdentifier = Category.LoopNotRunning.rawValue
111+
notification.threadIdentifier = Category.LoopNotRunning.rawValue
112+
113+
let request = UNNotificationRequest(
114+
identifier: "\(Category.LoopNotRunning.rawValue)\(failureInterval)",
115+
content: notification,
116+
trigger: UNTimeIntervalNotificationTrigger(
117+
timeInterval: failureInterval + gracePeriod,
118+
repeats: false
119+
)
120+
)
121+
122+
UNUserNotificationCenter.current().add(request)
115123
}
116124
}
117125

118126
static func sendPumpBatteryLowNotification() {
119-
let notification = UILocalNotification()
127+
let notification = UNMutableNotificationContent()
128+
129+
notification.title = NSLocalizedString("Pump Battery Low", comment: "The notification title for a low pump battery")
130+
notification.body = NSLocalizedString("Change the pump battery immediately", comment: "The notification alert describing a low pump battery")
131+
notification.sound = UNNotificationSound.default()
132+
notification.categoryIdentifier = Category.PumpBatteryLow.rawValue
120133

121-
notification.alertTitle = NSLocalizedString("Pump Battery Low", comment: "The notification title for a low pump battery")
122-
notification.alertBody = NSLocalizedString("Change the pump battery immediately", comment: "The notification alert describing a low pump battery")
123-
notification.soundName = UILocalNotificationDefaultSoundName
124-
notification.category = Category.PumpBatteryLow.rawValue
134+
let request = UNNotificationRequest(
135+
identifier: Category.PumpBatteryLow.rawValue,
136+
content: notification,
137+
trigger: nil
138+
)
125139

126-
UIApplication.shared.presentLocalNotificationNow(notification)
140+
UNUserNotificationCenter.current().add(request)
127141
}
128142

129143
static func sendPumpReservoirEmptyNotification() {
130-
let notification = UILocalNotification()
131-
132-
notification.alertTitle = NSLocalizedString("Pump Reservoir Empty", comment: "The notification title for an empty pump reservoir")
133-
notification.alertBody = NSLocalizedString("Change the pump reservoir now", comment: "The notification alert describing an empty pump reservoir")
134-
notification.soundName = UILocalNotificationDefaultSoundName
135-
notification.category = Category.PumpReservoirEmpty.rawValue
136-
137-
// TODO: Add an action to Suspend the pump
144+
let notification = UNMutableNotificationContent()
145+
146+
notification.title = NSLocalizedString("Pump Reservoir Empty", comment: "The notification title for an empty pump reservoir")
147+
notification.body = NSLocalizedString("Change the pump reservoir now", comment: "The notification alert describing an empty pump reservoir")
148+
notification.sound = UNNotificationSound.default()
149+
notification.categoryIdentifier = Category.PumpReservoirEmpty.rawValue
150+
151+
let request = UNNotificationRequest(
152+
// Not a typo: this should replace any pump reservoir low notifications
153+
identifier: Category.PumpReservoirLow.rawValue,
154+
content: notification,
155+
trigger: nil
156+
)
138157

139-
UIApplication.shared.presentLocalNotificationNow(notification)
158+
UNUserNotificationCenter.current().add(request)
140159
}
141160

142161
static func sendPumpReservoirLowNotificationForAmount(_ units: Double, andTimeRemaining remaining: TimeInterval?) {
143-
let notification = UILocalNotification()
162+
let notification = UNMutableNotificationContent()
144163

145-
notification.alertTitle = NSLocalizedString("Pump Reservoir Low", comment: "The notification title for a low pump reservoir")
164+
notification.title = NSLocalizedString("Pump Reservoir Low", comment: "The notification title for a low pump reservoir")
146165

147166
let unitsString = NumberFormatter.localizedString(from: NSNumber(value: units), number: .decimal)
148167

@@ -154,14 +173,20 @@ struct NotificationManager {
154173
intervalFormatter.includesTimeRemainingPhrase = true
155174

156175
if let remaining = remaining, let timeString = intervalFormatter.string(from: remaining) {
157-
notification.alertBody = String(format: NSLocalizedString("%1$@ U left: %2$@", comment: "Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining)"), unitsString, timeString)
176+
notification.body = String(format: NSLocalizedString("%1$@ U left: %2$@", comment: "Low reservoir alert with time remaining format string. (1: Number of units remaining)(2: approximate time remaining)"), unitsString, timeString)
158177
} else {
159-
notification.alertBody = String(format: NSLocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString)
178+
notification.body = String(format: NSLocalizedString("%1$@ U left", comment: "Low reservoir alert format string. (1: Number of units remaining)"), unitsString)
160179
}
161180

162-
notification.soundName = UILocalNotificationDefaultSoundName
163-
notification.category = Category.PumpReservoirLow.rawValue
181+
notification.sound = UNNotificationSound.default()
182+
notification.categoryIdentifier = Category.PumpReservoirLow.rawValue
183+
184+
let request = UNNotificationRequest(
185+
identifier: Category.PumpReservoirLow.rawValue,
186+
content: notification,
187+
trigger: nil
188+
)
164189

165-
UIApplication.shared.presentLocalNotificationNow(notification)
190+
UNUserNotificationCenter.current().add(request)
166191
}
167192
}

Loop/Managers/WatchDataManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ final class WatchDataManager: NSObject, WCSessionDelegate {
160160
}
161161
case SetBolusUserInfo.name?:
162162
if let bolus = SetBolusUserInfo(rawValue: message as SetBolusUserInfo.RawValue) {
163-
self.deviceDataManager.enactBolus(bolus.value) { (error) in
163+
self.deviceDataManager.enactBolus(units: bolus.value) { (error) in
164164
if error != nil {
165165
NotificationManager.sendBolusFailureNotificationForAmount(bolus.value, atStartDate: bolus.startDate)
166166
} else {

0 commit comments

Comments
 (0)