Skip to content

Commit 4d4aa88

Browse files
committed
Add profile expiration alert
1 parent c4fc328 commit 4d4aa88

File tree

5 files changed

+84
-0
lines changed

5 files changed

+84
-0
lines changed

Common/Extensions/NSTimeInterval.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ extension TimeInterval {
1818
return TimeInterval(minutes: minutes)
1919
}
2020

21+
2122
static func hours(_ hours: Double) -> TimeInterval {
2223
return TimeInterval(hours: hours)
2324
}
25+
26+
static func days(_ days: Double) -> TimeInterval {
27+
return TimeInterval(days: days)
28+
}
2429

2530
init(minutes: Double) {
2631
self.init(minutes * 60)
@@ -30,6 +35,10 @@ extension TimeInterval {
3035
self.init(minutes: hours * 60)
3136
}
3237

38+
init(days: Double) {
39+
self.init(hours: days * 24)
40+
}
41+
3342
var minutes: Double {
3443
return self / 60.0
3544
}

Loop.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@
376376
C17824A61E1AF91F00D9D25C /* ManualBolusRecommendation.swift in Sources */ = {isa = PBXBuildFile; fileRef = C17824A41E1AD4D100D9D25C /* ManualBolusRecommendation.swift */; };
377377
C1814B86225E507C008D2D8E /* Sequence.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1814B85225E507C008D2D8E /* Sequence.swift */; };
378378
C18C8C511D5A351900E043FB /* NightscoutDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */; };
379+
C19D65FE26D13F43003BBDE0 /* ProfileExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19D65FD26D13F43003BBDE0 /* ProfileExpirationAlerter.swift */; };
379380
C19E96DF23D275F8003F79B0 /* LoopCompletionFreshness.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */; };
380381
C19E96E023D275FA003F79B0 /* LoopCompletionFreshness.swift in Sources */ = {isa = PBXBuildFile; fileRef = C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */; };
381382
C1A3EED2235233E1007672E3 /* DerivedAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */; };
@@ -1092,6 +1093,7 @@
10921093
C18A491422FCC22900FDA733 /* build-derived-watch-assets.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "build-derived-watch-assets.sh"; sourceTree = "<group>"; };
10931094
C18A491522FCC22900FDA733 /* copy-plugins.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = "copy-plugins.sh"; sourceTree = "<group>"; };
10941095
C18C8C501D5A351900E043FB /* NightscoutDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NightscoutDataManager.swift; sourceTree = "<group>"; };
1096+
C19D65FD26D13F43003BBDE0 /* ProfileExpirationAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileExpirationAlerter.swift; sourceTree = "<group>"; };
10951097
C19E96DD23D2733F003F79B0 /* LoopCompletionFreshness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoopCompletionFreshness.swift; sourceTree = "<group>"; };
10961098
C1A3EED1235233E1007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = "<group>"; };
10971099
C1A3EED323523551007672E3 /* DerivedAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = DerivedAssets.xcassets; sourceTree = "<group>"; };
@@ -1662,6 +1664,7 @@
16621664
89ADE13A226BFA0F0067222B /* TestingScenariosManager.swift */,
16631665
4328E0341CFC0AE100E199AA /* WatchDataManager.swift */,
16641666
E9BB27AA23B85C3500FB4987 /* SleepStore.swift */,
1667+
C19D65FD26D13F43003BBDE0 /* ProfileExpirationAlerter.swift */,
16651668
);
16661669
path = Managers;
16671670
sourceTree = "<group>";
@@ -2623,6 +2626,7 @@
26232626
C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */,
26242627
43C513191E864C4E001547C7 /* GlucoseRangeSchedule.swift in Sources */,
26252628
43A51E1F1EB6D62A000736CC /* CarbAbsorptionViewController.swift in Sources */,
2629+
C19D65FE26D13F43003BBDE0 /* ProfileExpirationAlerter.swift in Sources */,
26262630
43776F901B8022E90074EA36 /* AppDelegate.swift in Sources */,
26272631
4372E48B213CB5F00068E043 /* Double.swift in Sources */,
26282632
430B29932041F5B300BA9F93 /* UserDefaults+Loop.swift in Sources */,

Loop/AppDelegate.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
9393
}
9494

9595
func applicationDidBecomeActive(_ application: UIApplication) {
96+
ProfileExpirationAlerter.alertIfNeeded(viewControllerToPresentFrom: rootViewController)
9697
deviceManager?.updatePumpManagerBLEHeartbeatPreference()
9798
}
9899

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
//
2+
// ProfileExpirationAlerter.swift
3+
// Loop
4+
//
5+
// Created by Pete Schwamb on 8/21/21.
6+
// Copyright © 2021 LoopKit Authors. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import UserNotifications
11+
import LoopCore
12+
13+
14+
class ProfileExpirationAlerter {
15+
16+
static let expirationAlertWindow: TimeInterval = .days(20)
17+
18+
static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) {
19+
20+
let now = Date()
21+
22+
guard let profileExpiration = Bundle.main.profileExpiration, now > profileExpiration - expirationAlertWindow else {
23+
return
24+
}
25+
26+
let timeUntilExpiration = profileExpiration.timeIntervalSince(now)
27+
28+
let minimumTimeBetweenAlerts: TimeInterval = timeUntilExpiration > .hours(24) ? .days(2) : .hours(1)
29+
30+
if let lastAlert = UserDefaults.appGroup?.lastProfileExpirationAlertDate {
31+
guard now > lastAlert + minimumTimeBetweenAlerts else {
32+
return
33+
}
34+
}
35+
36+
let formatter = DateComponentsFormatter()
37+
formatter.allowedUnits = [.day, .hour]
38+
formatter.unitsStyle = .full
39+
formatter.zeroFormattingBehavior = .dropLeading
40+
formatter.maximumUnitCount = 1
41+
let timeUntilExpirationStr = formatter.string(from: timeUntilExpiration)
42+
43+
let dialog = UIAlertController(
44+
title: NSLocalizedString("Profile Expires Soon", comment: "The title for notification of near profile expiration"),
45+
message: String(format: NSLocalizedString("Loop will stop working in %@. You will need to update Loop before that, with a new profile.", comment: "Format string for body for notification of near profile expiration. (1: amount of time until expiration"), timeUntilExpirationStr!),
46+
preferredStyle: .alert)
47+
dialog.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
48+
dialog.addAction(UIAlertAction(title: "More Info", style: .default, handler: { (_) in
49+
UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!)
50+
}))
51+
viewControllerToPresentFrom.present(dialog, animated: true, completion: nil)
52+
53+
UserDefaults.appGroup?.lastProfileExpirationAlertDate = now
54+
}
55+
}

LoopCore/NSUserDefaults.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extension UserDefaults {
2222
case overrideHistory = "com.tidepool.loopkit.overrideHistory"
2323
case lastBedtimeQuery = "com.loopkit.Loop.lastBedtimeQuery"
2424
case bedtime = "com.loopkit.Loop.bedtime"
25+
case lastProfileExpirationAlertDate = "com.loopkit.Loop.lastProfileExpirationAlertDate"
2526
}
2627

2728
public static let appGroup = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)
@@ -179,4 +180,18 @@ extension UserDefaults {
179180
set(newValue, forKey: Key.bedtime.rawValue)
180181
}
181182
}
183+
184+
public var lastProfileExpirationAlertDate: Date? {
185+
get {
186+
if let rawValue = object(forKey: Key.lastProfileExpirationAlertDate.rawValue) as? Date {
187+
return rawValue
188+
} else {
189+
return nil
190+
}
191+
}
192+
set {
193+
set(newValue, forKey: Key.lastProfileExpirationAlertDate.rawValue)
194+
}
195+
}
196+
182197
}

0 commit comments

Comments
 (0)