Skip to content

Commit db5be41

Browse files
committed
add customization for testflight_expiration_warning
1 parent da61747 commit db5be41

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
diff --git a/Loop/Loop/Managers/ProfileExpirationAlerter.swift b/Loop/Managers/ProfileExpirationAlerter.swift
2+
index a28d4527..068e6327 100644
3+
--- a/Loop/Loop/Managers/ProfileExpirationAlerter.swift
4+
+++ b/Loop/Loop/Managers/ProfileExpirationAlerter.swift
5+
@@ -19,13 +19,19 @@ class ProfileExpirationAlerter {
6+
static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) {
7+
8+
let now = Date()
9+
-
10+
- guard let profileExpiration = Bundle.main.profileExpiration, now > profileExpiration - expirationAlertWindow else {
11+
+
12+
+ guard let profileExpiration = Bundle.main.profileExpiration else {
13+
return
14+
}
15+
16+
- let timeUntilExpiration = profileExpiration.timeIntervalSince(now)
17+
+ let expirationDate = calculateExpirationDate(profileExpiration: profileExpiration)
18+
+
19+
+ let timeUntilExpiration = expirationDate.timeIntervalSince(now)
20+
21+
+ if timeUntilExpiration > expirationAlertWindow {
22+
+ return
23+
+ }
24+
+
25+
let minimumTimeBetweenAlerts: TimeInterval = timeUntilExpiration > .hours(24) ? .days(2) : .hours(1)
26+
27+
if let lastAlertDate = UserDefaults.appGroup?.lastProfileExpirationAlertDate {
28+
@@ -43,31 +49,48 @@ class ProfileExpirationAlerter {
29+
30+
let alertMessage = createVerboseAlertMessage(timeUntilExpirationStr: timeUntilExpirationStr!)
31+
32+
- let dialog = UIAlertController(
33+
- title: NSLocalizedString("Profile Expires Soon", comment: "The title for notification of upcoming profile expiration"),
34+
- message: alertMessage,
35+
- preferredStyle: .alert)
36+
- dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming profile expiration"), style: .default, handler: nil))
37+
- dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming profile expiration"), style: .default, handler: { (_) in
38+
- UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!)
39+
- }))
40+
+ var dialog: UIAlertController
41+
+ if isTestFlightBuild() {
42+
+ dialog = UIAlertController(
43+
+ title: NSLocalizedString("TestFlight Expires Soon", comment: "The title for notification of upcoming TestFlight expiration"),
44+
+ message: alertMessage,
45+
+ preferredStyle: .alert)
46+
+ dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming TestFlight expiration"), style: .default, handler: nil))
47+
+ dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming TestFlight expiration"), style: .default, handler: { (_) in
48+
+ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/gh-actions/gh-update/")!)
49+
+ }))
50+
+
51+
+ } else {
52+
+ dialog = UIAlertController(
53+
+ title: NSLocalizedString("Profile Expires Soon", comment: "The title for notification of upcoming profile expiration"),
54+
+ message: alertMessage,
55+
+ preferredStyle: .alert)
56+
+ dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming profile expiration"), style: .default, handler: nil))
57+
+ dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming profile expiration"), style: .default, handler: { (_) in
58+
+ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!)
59+
+ }))
60+
+ }
61+
viewControllerToPresentFrom.present(dialog, animated: true, completion: nil)
62+
63+
UserDefaults.appGroup?.lastProfileExpirationAlertDate = now
64+
}
65+
66+
static func createVerboseAlertMessage(timeUntilExpirationStr:String) -> String {
67+
- return String(format: NSLocalizedString("%1$@ will stop working in %2$@. You will need to update before that, with a new provisioning profile.", comment: "Format string for body for notification of upcoming provisioning profile expiration. (1: app name) (2: amount of time until expiration"), Bundle.main.bundleDisplayName, timeUntilExpirationStr)
68+
+ if isTestFlightBuild() {
69+
+ return String(format: NSLocalizedString("%1$@ will stop working in %2$@. You will need to rebuild before that.", comment: "Format string for body for notification of upcoming expiration. (1: app name) (2: amount of time until expiration"), Bundle.main.bundleDisplayName, timeUntilExpirationStr)
70+
+ } else {
71+
+ return String(format: NSLocalizedString("%1$@ will stop working in %2$@. You will need to update before that, with a new provisioning profile.", comment: "Format string for body for notification of upcoming provisioning profile expiration. (1: app name) (2: amount of time until expiration"), Bundle.main.bundleDisplayName, timeUntilExpirationStr)
72+
+ }
73+
}
74+
75+
- static func isNearProfileExpiration(profileExpiration:Date) -> Bool {
76+
- return profileExpiration.timeIntervalSinceNow < settingsPageExpirationWarningModeWindow
77+
+ static func isNearExpiration(expirationDate:Date) -> Bool {
78+
+ return expirationDate.timeIntervalSinceNow < settingsPageExpirationWarningModeWindow
79+
}
80+
81+
- static func createProfileExpirationSettingsMessage(profileExpiration:Date) -> String {
82+
- let nearExpiration = isNearProfileExpiration(profileExpiration: profileExpiration)
83+
+ static func createProfileExpirationSettingsMessage(expirationDate:Date) -> String {
84+
+ let nearExpiration = isNearExpiration(expirationDate: expirationDate)
85+
let maxUnitCount = nearExpiration ? 2 : 1 // only include hours in the msg if near expiration
86+
- let readableRelativeTime: String? = relativeTimeFormatter(maxUnitCount: maxUnitCount).string(from: profileExpiration.timeIntervalSinceNow)
87+
+ let readableRelativeTime: String? = relativeTimeFormatter(maxUnitCount: maxUnitCount).string(from: expirationDate.timeIntervalSinceNow)
88+
let relativeTimeRemaining: String = readableRelativeTime ?? NSLocalizedString("Unknown time", comment: "Unknown amount of time in settings' profile expiration section")
89+
let verboseMessage = createVerboseAlertMessage(timeUntilExpirationStr: relativeTimeRemaining)
90+
let conciseMessage = relativeTimeRemaining + NSLocalizedString(" remaining", comment: "remaining time in setting's profile expiration section")
91+
@@ -81,6 +104,50 @@ class ProfileExpirationAlerter {
92+
formatter.unitsStyle = .full
93+
formatter.zeroFormattingBehavior = .dropLeading
94+
formatter.maximumUnitCount = maxUnitCount
95+
- return formatter;
96+
+ return formatter
97+
+ }
98+
+
99+
+ static func buildDate() -> Date? {
100+
+ let dateFormatter = DateFormatter()
101+
+ dateFormatter.dateFormat = "EEE MMM d HH:mm:ss 'UTC' yyyy"
102+
+ dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Set locale to ensure parsing works
103+
+ dateFormatter.timeZone = TimeZone(identifier: "UTC")
104+
+
105+
+ guard let dateString = Bundle.main.buildDateString,
106+
+ let date = dateFormatter.date(from: dateString) else {
107+
+ return nil
108+
+ }
109+
+
110+
+ return date
111+
+ }
112+
+
113+
+ static func isTestFlightBuild() -> Bool {
114+
+ // If an "embedded.mobileprovision" is present in the main bundle, then
115+
+ // this is an Xcode, Ad-Hoc, or Enterprise distribution. Return false.
116+
+ if Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") != nil {
117+
+ return false
118+
+ }
119+
+
120+
+ // If an app store receipt is not present in the main bundle, then we cannot
121+
+ // say whether this is a TestFlight or App Store distribution. Return false.
122+
+ guard let receiptName = Bundle.main.appStoreReceiptURL?.lastPathComponent else {
123+
+ return false
124+
+ }
125+
+
126+
+ // A TestFlight distribution presents a "sandboxReceipt", while an App Store
127+
+ // distribution presents a "receipt". Return true if we have a TestFlight receipt.
128+
+ return "sandboxReceipt".caseInsensitiveCompare(receiptName) == .orderedSame
129+
+ }
130+
+
131+
+ static func calculateExpirationDate(profileExpiration: Date) -> Date {
132+
+ let isTestFlight = isTestFlightBuild()
133+
+
134+
+ if isTestFlight, let buildDate = buildDate() {
135+
+ let testflightExpiration = Calendar.current.date(byAdding: .day, value: 90, to: buildDate)!
136+
+
137+
+ return profileExpiration < testflightExpiration ? profileExpiration : testflightExpiration
138+
+ } else {
139+
+ return profileExpiration
140+
+ }
141+
}
142+
}
143+
diff --git a/Loop/Loop/Views/SettingsView.swift b/Loop/Loop/Views/SettingsView.swift
144+
index 90859273..e41ebfa2 100644
145+
--- a/Loop/Loop/Views/SettingsView.swift
146+
+++ b/Loop/Loop/Views/SettingsView.swift
147+
@@ -343,25 +343,48 @@ extension SettingsView {
148+
DIY loop specific component to show users the amount of time remaining on their build before a rebuild is necessary.
149+
*/
150+
private func profileExpirationSection(profileExpiration:Date) -> some View {
151+
- let nearExpiration : Bool = ProfileExpirationAlerter.isNearProfileExpiration(profileExpiration: profileExpiration)
152+
- let profileExpirationMsg = ProfileExpirationAlerter.createProfileExpirationSettingsMessage(profileExpiration: profileExpiration)
153+
- let readableExpirationTime = Self.dateFormatter.string(from: profileExpiration)
154+
+ let expirationDate = ProfileExpirationAlerter.calculateExpirationDate(profileExpiration: profileExpiration)
155+
+ let isTestFlight = ProfileExpirationAlerter.isTestFlightBuild()
156+
157+
- return Section(header: SectionHeader(label: NSLocalizedString("App Profile", comment: "Settings app profile section")),
158+
- footer: Text(NSLocalizedString("Profile expires ", comment: "Time that profile expires") + readableExpirationTime)) {
159+
- if(nearExpiration) {
160+
- Text(profileExpirationMsg).foregroundColor(.red)
161+
- } else {
162+
- HStack {
163+
- Text("Profile Expiration", comment: "Settings App Profile expiration view")
164+
- Spacer()
165+
- Text(profileExpirationMsg).foregroundColor(Color.secondary)
166+
+ let nearExpiration : Bool = ProfileExpirationAlerter.isNearExpiration(expirationDate: expirationDate)
167+
+ let profileExpirationMsg = ProfileExpirationAlerter.createProfileExpirationSettingsMessage(expirationDate: expirationDate)
168+
+ let readableExpirationTime = Self.dateFormatter.string(from: expirationDate)
169+
+
170+
+ if isTestFlight {
171+
+ return Section(header: SectionHeader(label: NSLocalizedString("TestFlight", comment: "Settings app TestFlight section")),
172+
+ footer: Text(NSLocalizedString("TestFlight expires ", comment: "Time that build expires") + readableExpirationTime)) {
173+
+ if(nearExpiration) {
174+
+ Text(profileExpirationMsg).foregroundColor(.red)
175+
+ } else {
176+
+ HStack {
177+
+ Text("TestFlight Expiration", comment: "Settings TestFlight expiration view")
178+
+ Spacer()
179+
+ Text(profileExpirationMsg).foregroundColor(Color.secondary)
180+
+ }
181+
+ }
182+
+ Button(action: {
183+
+ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/gh-actions/gh-update/")!)
184+
+ }) {
185+
+ Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update"))
186+
}
187+
}
188+
- Button(action: {
189+
- UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!)
190+
- }) {
191+
- Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update"))
192+
+ } else {
193+
+ return Section(header: SectionHeader(label: NSLocalizedString("App Profile", comment: "Settings app profile section")),
194+
+ footer: Text(NSLocalizedString("Profile expires ", comment: "Time that profile expires") + readableExpirationTime)) {
195+
+ if(nearExpiration) {
196+
+ Text(profileExpirationMsg).foregroundColor(.red)
197+
+ } else {
198+
+ HStack {
199+
+ Text("Profile Expiration", comment: "Settings App Profile expiration view")
200+
+ Spacer()
201+
+ Text(profileExpirationMsg).foregroundColor(Color.secondary)
202+
+ }
203+
+ }
204+
+ Button(action: {
205+
+ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!)
206+
+ }) {
207+
+ Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update"))
208+
+ }
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)