diff --git a/test11.patch b/test11.patch new file mode 100644 index 0000000..765ad56 --- /dev/null +++ b/test11.patch @@ -0,0 +1,509 @@ +diff --git a/Common/Models/BuildDetails.swift b/Common/Models/BuildDetails.swift +index 63517e7e..0badb257 100644 +--- a/Common/Models/BuildDetails.swift ++++ b/Common/Models/BuildDetails.swift +@@ -65,5 +65,9 @@ class BuildDetails { + var workspaceGitBranch: String? { + return dict["com-loopkit-LoopWorkspace-git-branch"] as? String + } ++ ++ var isGitHubBuild: Bool? { ++ return dict["com-loopkit-GitHub-build"] as? Bool ++ } + } + +diff --git a/Loop.xcodeproj/project.pbxproj b/Loop.xcodeproj/project.pbxproj +index aaa0a470..426e3130 100644 +--- a/Loop.xcodeproj/project.pbxproj ++++ b/Loop.xcodeproj/project.pbxproj +@@ -479,7 +479,7 @@ + C1EF747228D6A44A00C8C083 /* CrashRecoveryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */; }; + C1F00C60285A802A006302C5 /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; }; + C1F00C78285A8256006302C5 /* SwiftCharts in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; +- C1F2075C26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */; }; ++ C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */; }; + C1F7822627CC056900C0919A /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F7822527CC056900C0919A /* SettingsManager.swift */; }; + C1F8B243223E73FD00DD66CF /* BolusProgressTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */; }; + C1FB428C217806A400FAB378 /* StateColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428B217806A300FAB378 /* StateColorPalette.swift */; }; +@@ -487,6 +487,7 @@ + C1FB428F217921D600FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + C1FB4290217922A100FAB378 /* PumpManagerUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1FB428E217921D600FAB378 /* PumpManagerUI.swift */; }; + DD3DBD292A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD3DBD282A33AFE9000F8B5B /* IntegralRetrospectiveCorrectionSelectionView.swift */; }; ++ DD8AA12C2A9C915600A4F583 /* ProvisioningProfile in Frameworks */ = {isa = PBXBuildFile; productRef = DD8AA12B2A9C915600A4F583 /* ProvisioningProfile */; }; + DDC389F62A2B61750066E2E8 /* ApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F52A2B61750066E2E8 /* ApplicationFactorStrategy.swift */; }; + DDC389F82A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F72A2B620B0066E2E8 /* GlucoseBasedApplicationFactorStrategy.swift */; }; + DDC389FA2A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC389F92A2B62470066E2E8 /* ConstantApplicationFactorStrategy.swift */; }; +@@ -1565,7 +1566,7 @@ + C1EB0D22299581D900628475 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/ckcomplication.strings; sourceTree = ""; }; + C1EE9E802A38D0FB0064784A /* BuildDetails.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = BuildDetails.plist; sourceTree = ""; }; + C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashRecoveryManager.swift; sourceTree = ""; }; +- C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileExpirationAlerter.swift; sourceTree = ""; }; ++ C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExpirationAlerter.swift; sourceTree = ""; }; + C1F48FF62995821600C8BD69 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + C1F48FF72995821600C8BD69 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; + C1F48FF82995821600C8BD69 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Localizable.strings; sourceTree = ""; }; +@@ -1749,6 +1750,7 @@ + C19C8BC328651EAE0056D5E4 /* LoopTestingKit.framework in Frameworks */, + C1735B1E2A0809830082BB8A /* ZIPFoundation in Frameworks */, + C19C8BCE28651F520056D5E4 /* LoopKitUI.framework in Frameworks */, ++ DD8AA12C2A9C915600A4F583 /* ProvisioningProfile in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +@@ -2307,7 +2309,7 @@ + 1DA6499D2441266400F61E75 /* Alerts */, + E95D37FF24EADE68005E2F50 /* Store Protocols */, + E9B355232935906B0076AB04 /* Missed Meal Detection */, +- C1F2075B26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift */, ++ C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */, + A96DAC2B2838F31200D94E38 /* SharedLogging.swift */, + 7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */, + 84AA81E42A4A3981000B658B /* DeeplinkManager.swift */, +@@ -3017,6 +3019,7 @@ + C1F00C5F285A802A006302C5 /* SwiftCharts */, + C1D6EE9F2A06C7270047DE5C /* MKRingProgressView */, + C1735B1D2A0809830082BB8A /* ZIPFoundation */, ++ DD8AA12B2A9C915600A4F583 /* ProvisioningProfile */, + ); + productName = Loop; + productReference = 43776F8C1B8022E90074EA36 /* Loop.app */; +@@ -3318,6 +3321,7 @@ + C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */, + C1D6EE9E2A06C7270047DE5C /* XCRemoteSwiftPackageReference "MKRingProgressView" */, + C1735B1C2A0809830082BB8A /* XCRemoteSwiftPackageReference "ZIPFoundation" */, ++ DD8AA12A2A9C915600A4F583 /* XCRemoteSwiftPackageReference "ProvisioningProfile" */, + ); + productRefGroup = 43776F8D1B8022E90074EA36 /* Products */; + projectDirPath = ""; +@@ -3654,7 +3658,7 @@ + C1D289B522F90A52003FFBD9 /* BasalDeliveryState.swift in Sources */, + 4F2C15821E074FC600E160D4 /* NSTimeInterval.swift in Sources */, + 4311FB9B1F37FE1B00D4C0A7 /* TitleSubtitleTextFieldTableViewCell.swift in Sources */, +- C1F2075C26D6F9B0007AB7EB /* ProfileExpirationAlerter.swift in Sources */, ++ C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */, + B4FEEF7D24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift in Sources */, + 142CB7592A60BF2E0075748A /* EditMode.swift in Sources */, + E95D380324EADF36005E2F50 /* CarbStoreProtocol.swift in Sources */, +@@ -5650,6 +5654,14 @@ + kind = branch; + }; + }; ++ DD8AA12A2A9C915600A4F583 /* XCRemoteSwiftPackageReference "ProvisioningProfile" */ = { ++ isa = XCRemoteSwiftPackageReference; ++ repositoryURL = "https://github.com/ChrisMash/ProvisioningProfile.git"; ++ requirement = { ++ branch = main; ++ kind = branch; ++ }; ++ }; + /* End XCRemoteSwiftPackageReference section */ + + /* Begin XCSwiftPackageProductDependency section */ +@@ -5683,6 +5695,11 @@ + package = C1CCF10B2858F4F70035389C /* XCRemoteSwiftPackageReference "SwiftCharts" */; + productName = SwiftCharts; + }; ++ DD8AA12B2A9C915600A4F583 /* ProvisioningProfile */ = { ++ isa = XCSwiftPackageProductDependency; ++ package = DD8AA12A2A9C915600A4F583 /* XCRemoteSwiftPackageReference "ProvisioningProfile" */; ++ productName = ProvisioningProfile; ++ }; + /* End XCSwiftPackageProductDependency section */ + + /* Begin XCVersionGroup section */ +diff --git a/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +new file mode 100644 +index 00000000..ebf983aa +--- /dev/null ++++ b/Loop.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +@@ -0,0 +1,41 @@ ++{ ++ "pins" : [ ++ { ++ "identity" : "mkringprogressview", ++ "kind" : "remoteSourceControl", ++ "location" : "https://github.com/maxkonovalov/MKRingProgressView.git", ++ "state" : { ++ "branch" : "master", ++ "revision" : "660888aab1d2ab0ed7eb9eb53caec12af4955fa7" ++ } ++ }, ++ { ++ "identity" : "provisioningprofile", ++ "kind" : "remoteSourceControl", ++ "location" : "https://github.com/ChrisMash/ProvisioningProfile.git", ++ "state" : { ++ "branch" : "main", ++ "revision" : "c30888e1addb29d872ec779613fb715012b72312" ++ } ++ }, ++ { ++ "identity" : "swiftcharts", ++ "kind" : "remoteSourceControl", ++ "location" : "https://github.com/ivanschuetz/SwiftCharts", ++ "state" : { ++ "branch" : "master", ++ "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2" ++ } ++ }, ++ { ++ "identity" : "zipfoundation", ++ "kind" : "remoteSourceControl", ++ "location" : "https://github.com/LoopKit/ZIPFoundation.git", ++ "state" : { ++ "branch" : "stream-entry", ++ "revision" : "c67b7509ec82ee2b4b0ab3f97742b94ed9692494" ++ } ++ } ++ ], ++ "version" : 2 ++} +diff --git a/Loop/Managers/AppExpirationAlerter.swift b/Loop/Managers/AppExpirationAlerter.swift +new file mode 100644 +index 00000000..6214e9e9 +--- /dev/null ++++ b/Loop/Managers/AppExpirationAlerter.swift +@@ -0,0 +1,135 @@ ++// ++// AppExpirationAlerter.swift ++// Loop ++// ++// Created by Pete Schwamb on 8/21/21. ++// Copyright © 2021 LoopKit Authors. All rights reserved. ++// ++ ++import Foundation ++import UserNotifications ++import LoopCore ++ ++ ++class AppExpirationAlerter { ++ ++ static let expirationAlertWindow: TimeInterval = .days(20) ++ static let settingsPageExpirationWarningModeWindow: TimeInterval = .days(3) ++ ++ static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) { ++ ++ let now = Date() ++ ++ guard let profileExpiration = BuildDetails.default.profileExpiration, now > profileExpiration - expirationAlertWindow else { ++ return ++ } ++ ++ let expirationDate = calculateExpirationDate(profileExpiration: profileExpiration) ++ ++ let timeUntilExpiration = expirationDate.timeIntervalSince(now) ++ ++ let minimumTimeBetweenAlerts: TimeInterval = timeUntilExpiration > .hours(24) ? .days(2) : .hours(1) ++ ++ if let lastAlertDate = UserDefaults.appGroup?.lastProfileExpirationAlertDate { ++ guard now > lastAlertDate + minimumTimeBetweenAlerts else { ++ return ++ } ++ } ++ ++ let formatter = DateComponentsFormatter() ++ formatter.allowedUnits = [.day, .hour] ++ formatter.unitsStyle = .full ++ formatter.zeroFormattingBehavior = .dropLeading ++ formatter.maximumUnitCount = 1 ++ let timeUntilExpirationStr = formatter.string(from: timeUntilExpiration) ++ ++ let alertMessage = createVerboseAlertMessage(timeUntilExpirationStr: timeUntilExpirationStr!) ++ ++ var dialog: UIAlertController ++ if isTestFlightBuild() { ++ dialog = UIAlertController( ++ title: NSLocalizedString("TestFlight Expires Soon", comment: "The title for notification of upcoming TestFlight expiration"), ++ message: alertMessage, ++ preferredStyle: .alert) ++ dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming profile expiration"), style: .default, handler: nil)) ++ dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming TestFlight expiration"), style: .default, handler: { (_) in ++ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/gh-actions/gh-update/")!) ++ })) ++ ++ } else { ++ dialog = UIAlertController( ++ title: NSLocalizedString("Profile Expires Soon", comment: "The title for notification of upcoming profile expiration"), ++ message: alertMessage, ++ preferredStyle: .alert) ++ dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming profile expiration"), style: .default, handler: nil)) ++ dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming profile expiration"), style: .default, handler: { (_) in ++ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!) ++ })) ++ } ++ viewControllerToPresentFrom.present(dialog, animated: true, completion: nil) ++ ++ UserDefaults.appGroup?.lastProfileExpirationAlertDate = now ++ } ++ ++ static func createVerboseAlertMessage(timeUntilExpirationStr:String) -> String { ++ if isTestFlightBuild() { ++ 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) ++ } else { ++ 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 provisioning profile expiration. (1: app name) (2: amount of time until expiration"), Bundle.main.bundleDisplayName, timeUntilExpirationStr) ++ } ++ } ++ ++ static func isNearExpiration(expirationDate:Date) -> Bool { ++ return expirationDate.timeIntervalSinceNow < settingsPageExpirationWarningModeWindow ++ } ++ ++ static func createProfileExpirationSettingsMessage(expirationDate:Date) -> String { ++ let nearExpiration = isNearExpiration(expirationDate: expirationDate) ++ let maxUnitCount = nearExpiration ? 2 : 1 // only include hours in the msg if near expiration ++ let readableRelativeTime: String? = relativeTimeFormatter(maxUnitCount: maxUnitCount).string(from: expirationDate.timeIntervalSinceNow) ++ let relativeTimeRemaining: String = readableRelativeTime ?? NSLocalizedString("Unknown time", comment: "Unknown amount of time in settings' profile expiration section") ++ let verboseMessage = createVerboseAlertMessage(timeUntilExpirationStr: relativeTimeRemaining) ++ let conciseMessage = relativeTimeRemaining + NSLocalizedString(" remaining", comment: "remaining time in setting's profile expiration section") ++ return nearExpiration ? verboseMessage : conciseMessage ++ } ++ ++ private static func relativeTimeFormatter(maxUnitCount:Int) -> DateComponentsFormatter { ++ let formatter = DateComponentsFormatter() ++ let includeHours = maxUnitCount == 2 ++ formatter.allowedUnits = includeHours ? [.day, .hour] : [.day] ++ formatter.unitsStyle = .full ++ formatter.zeroFormattingBehavior = .dropLeading ++ formatter.maximumUnitCount = maxUnitCount ++ return formatter; ++ } ++ ++ static func buildDate() -> Date? { ++ let dateFormatter = DateFormatter() ++ dateFormatter.dateFormat = "EEE MMM d HH:mm:ss 'UTC' yyyy" ++ dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Set locale to ensure parsing works ++ dateFormatter.timeZone = TimeZone(identifier: "UTC") ++ ++ guard let dateString = BuildDetails.default.buildDateString, ++ let date = dateFormatter.date(from: dateString) else { ++ return nil ++ } ++ ++ return date ++ } ++ ++ static func isTestFlightBuild() -> Bool { ++ return BuildDetails.default.isGitHubBuild ?? false ++ } ++ ++ static func calculateExpirationDate(profileExpiration: Date) -> Date { ++ let isTestFlight = isTestFlightBuild() ++ ++ if isTestFlight, let buildDate = buildDate() { ++ let testflightExpiration = Calendar.current.date(byAdding: .day, value: 90, to: buildDate)! ++ ++ return profileExpiration < testflightExpiration ? profileExpiration : testflightExpiration ++ } else { ++ return profileExpiration ++ } ++ } ++} +diff --git a/Loop/Managers/LoopAppManager.swift b/Loop/Managers/LoopAppManager.swift +index 43c62d12..9edf481b 100644 +--- a/Loop/Managers/LoopAppManager.swift ++++ b/Loop/Managers/LoopAppManager.swift +@@ -323,7 +323,7 @@ class LoopAppManager: NSObject { + + func didBecomeActive() { + if let rootViewController = rootViewController { +- ProfileExpirationAlerter.alertIfNeeded(viewControllerToPresentFrom: rootViewController) ++ AppExpirationAlerter.alertIfNeeded(viewControllerToPresentFrom: rootViewController) + } + settingsManager?.didBecomeActive() + deviceDataManager?.didBecomeActive() +diff --git a/Loop/Managers/ProfileExpirationAlerter.swift b/Loop/Managers/ProfileExpirationAlerter.swift +deleted file mode 100644 +index 3aa74273..00000000 +--- a/Loop/Managers/ProfileExpirationAlerter.swift ++++ /dev/null +@@ -1,86 +0,0 @@ +-// +-// ProfileExpirationAlerter.swift +-// Loop +-// +-// Created by Pete Schwamb on 8/21/21. +-// Copyright © 2021 LoopKit Authors. All rights reserved. +-// +- +-import Foundation +-import UserNotifications +-import LoopCore +- +- +-class ProfileExpirationAlerter { +- +- static let expirationAlertWindow: TimeInterval = .days(20) +- static let settingsPageExpirationWarningModeWindow: TimeInterval = .days(3) +- +- static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) { +- +- let now = Date() +- +- guard let profileExpiration = BuildDetails.default.profileExpiration, now > profileExpiration - expirationAlertWindow else { +- return +- } +- +- let timeUntilExpiration = profileExpiration.timeIntervalSince(now) +- +- let minimumTimeBetweenAlerts: TimeInterval = timeUntilExpiration > .hours(24) ? .days(2) : .hours(1) +- +- if let lastAlertDate = UserDefaults.appGroup?.lastProfileExpirationAlertDate { +- guard now > lastAlertDate + minimumTimeBetweenAlerts else { +- return +- } +- } +- +- let formatter = DateComponentsFormatter() +- formatter.allowedUnits = [.day, .hour] +- formatter.unitsStyle = .full +- formatter.zeroFormattingBehavior = .dropLeading +- formatter.maximumUnitCount = 1 +- let timeUntilExpirationStr = formatter.string(from: timeUntilExpiration) +- +- let alertMessage = createVerboseAlertMessage(timeUntilExpirationStr: timeUntilExpirationStr!) +- +- let dialog = UIAlertController( +- title: NSLocalizedString("Profile Expires Soon", comment: "The title for notification of upcoming profile expiration"), +- message: alertMessage, +- preferredStyle: .alert) +- dialog.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Text for ok action on notification of upcoming profile expiration"), style: .default, handler: nil)) +- dialog.addAction(UIAlertAction(title: NSLocalizedString("More Info", comment: "Text for more info action on notification of upcoming profile expiration"), style: .default, handler: { (_) in +- UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!) +- })) +- viewControllerToPresentFrom.present(dialog, animated: true, completion: nil) +- +- UserDefaults.appGroup?.lastProfileExpirationAlertDate = now +- } +- +- static func createVerboseAlertMessage(timeUntilExpirationStr:String) -> String { +- 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) +- } +- +- static func isNearProfileExpiration(profileExpiration:Date) -> Bool { +- return profileExpiration.timeIntervalSinceNow < settingsPageExpirationWarningModeWindow +- } +- +- static func createProfileExpirationSettingsMessage(profileExpiration:Date) -> String { +- let nearExpiration = isNearProfileExpiration(profileExpiration: profileExpiration) +- let maxUnitCount = nearExpiration ? 2 : 1 // only include hours in the msg if near expiration +- let readableRelativeTime: String? = relativeTimeFormatter(maxUnitCount: maxUnitCount).string(from: profileExpiration.timeIntervalSinceNow) +- let relativeTimeRemaining: String = readableRelativeTime ?? NSLocalizedString("Unknown time", comment: "Unknown amount of time in settings' profile expiration section") +- let verboseMessage = createVerboseAlertMessage(timeUntilExpirationStr: relativeTimeRemaining) +- let conciseMessage = relativeTimeRemaining + NSLocalizedString(" remaining", comment: "remaining time in setting's profile expiration section") +- return nearExpiration ? verboseMessage : conciseMessage +- } +- +- private static func relativeTimeFormatter(maxUnitCount:Int) -> DateComponentsFormatter { +- let formatter = DateComponentsFormatter() +- let includeHours = maxUnitCount == 2 +- formatter.allowedUnits = includeHours ? [.day, .hour] : [.day] +- formatter.unitsStyle = .full +- formatter.zeroFormattingBehavior = .dropLeading +- formatter.maximumUnitCount = maxUnitCount +- return formatter; +- } +-} +diff --git a/Loop/View Controllers/CommandResponseViewController.swift b/Loop/View Controllers/CommandResponseViewController.swift +index e14c41c8..4563cce5 100644 +--- a/Loop/View Controllers/CommandResponseViewController.swift ++++ b/Loop/View Controllers/CommandResponseViewController.swift +@@ -8,6 +8,7 @@ + + import Foundation + import LoopKitUI ++import ProvisioningProfile + + + extension CommandResponseViewController { +@@ -21,7 +22,7 @@ extension CommandResponseViewController { + completionHandler([ + "Use the Share button above to save this diagnostic report to aid investigating your problem. Issues can be filed at https://github.com/LoopKit/Loop/issues.", + "Generated: \(date)", +- "", ++ ProvisioningProfile.profile()?.formattedExpiryDate ?? "Not available", + report, + "", + ].joined(separator: "\n\n")) +diff --git a/Loop/Views/SettingsView.swift b/Loop/Views/SettingsView.swift +index 0b4cb551..a07d623f 100644 +--- a/Loop/Views/SettingsView.swift ++++ b/Loop/Views/SettingsView.swift +@@ -417,25 +417,48 @@ extension SettingsView { + DIY loop specific component to show users the amount of time remaining on their build before a rebuild is necessary. + */ + private func profileExpirationSection(profileExpiration:Date) -> some View { +- let nearExpiration : Bool = ProfileExpirationAlerter.isNearProfileExpiration(profileExpiration: profileExpiration) +- let profileExpirationMsg = ProfileExpirationAlerter.createProfileExpirationSettingsMessage(profileExpiration: profileExpiration) +- let readableExpirationTime = Self.dateFormatter.string(from: profileExpiration) ++ let expirationDate = AppExpirationAlerter.calculateExpirationDate(profileExpiration: profileExpiration) ++ let isTestFlight = AppExpirationAlerter.isTestFlightBuild() + +- return Section(header: SectionHeader(label: NSLocalizedString("App Profile", comment: "Settings app profile section")), +- footer: Text(NSLocalizedString("Profile expires ", comment: "Time that profile expires") + readableExpirationTime)) { +- if(nearExpiration) { +- Text(profileExpirationMsg).foregroundColor(.red) +- } else { +- HStack { +- Text("Profile Expiration", comment: "Settings App Profile expiration view") +- Spacer() +- Text(profileExpirationMsg).foregroundColor(Color.secondary) ++ let nearExpiration : Bool = AppExpirationAlerter.isNearExpiration(expirationDate: expirationDate) ++ let profileExpirationMsg = AppExpirationAlerter.createProfileExpirationSettingsMessage(expirationDate: expirationDate) ++ let readableExpirationTime = Self.dateFormatter.string(from: expirationDate) ++ ++ if isTestFlight { ++ return Section(header: SectionHeader(label: NSLocalizedString("TestFlight", comment: "Settings app TestFlight section")), ++ footer: Text(NSLocalizedString("TestFlight expires ", comment: "Time that build expires") + readableExpirationTime)) { ++ if(nearExpiration) { ++ Text(profileExpirationMsg).foregroundColor(.red) ++ } else { ++ HStack { ++ Text("TestFlight Expiration", comment: "Settings TestFlight expiration view") ++ Spacer() ++ Text(profileExpirationMsg).foregroundColor(Color.secondary) ++ } ++ } ++ Button(action: { ++ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/gh-actions/gh-update/")!) ++ }) { ++ Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update")) + } + } +- Button(action: { +- UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!) +- }) { +- Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update")) ++ } else { ++ return Section(header: SectionHeader(label: NSLocalizedString("App Profile", comment: "Settings app profile section")), ++ footer: Text(NSLocalizedString("Profile expires ", comment: "Time that profile expires") + readableExpirationTime)) { ++ if(nearExpiration) { ++ Text(profileExpirationMsg).foregroundColor(.red) ++ } else { ++ HStack { ++ Text("Profile Expiration", comment: "Settings App Profile expiration view") ++ Spacer() ++ Text(profileExpirationMsg).foregroundColor(Color.secondary) ++ } ++ } ++ Button(action: { ++ UIApplication.shared.open(URL(string: "https://loopkit.github.io/loopdocs/build/updating/")!) ++ }) { ++ Text(NSLocalizedString("How to update (LoopDocs)", comment: "The title text for how to update")) ++ } + } + } + } +diff --git a/Scripts/capture-build-details.sh b/Scripts/capture-build-details.sh +index 66f827d7..932e1c7b 100755 +--- a/Scripts/capture-build-details.sh ++++ b/Scripts/capture-build-details.sh +@@ -91,3 +91,8 @@ then + fi + popd . > /dev/null + fi ++ ++# Handle github action ++if [ -n "$GITHUB_ACTIONS" ]; then ++ plutil -replace com-loopkit-GitHub-build -bool true "${info_plist_path}" ++fi