Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Common/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct FeatureFlagConfiguration: Decodable {
let usePositiveMomentumAndRCForManualBoluses: Bool
let dynamicCarbAbsorptionEnabled: Bool
let adultChildInsulinModelSelectionEnabled: Bool
let profileExpirationSettingsViewEnabled: Bool


fileprivate init() {
Expand Down Expand Up @@ -212,6 +213,13 @@ struct FeatureFlagConfiguration: Decodable {
#endif

self.dynamicCarbAbsorptionEnabled = true

// ProfileExpirationSettingsView is inverse, since the default state is enabled.
#if PROFILE_EXPIRATION_SETTINGS_VIEW_DISABLED
self.profileExpirationSettingsViewEnabled = false
#else
self.profileExpirationSettingsViewEnabled = true
#endif
}
}

Expand Down Expand Up @@ -244,7 +252,8 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible {
"* simpleBolusCalculatorEnabled: \(simpleBolusCalculatorEnabled)",
"* usePositiveMomentumAndRCForManualBoluses: \(usePositiveMomentumAndRCForManualBoluses)",
"* dynamicCarbAbsorptionEnabled: \(dynamicCarbAbsorptionEnabled)",
"* adultChildInsulinModelSelectionEnabled: \(adultChildInsulinModelSelectionEnabled)"
"* adultChildInsulinModelSelectionEnabled: \(adultChildInsulinModelSelectionEnabled)",
"* profileExpirationSettingsViewEnabled: \(profileExpirationSettingsViewEnabled)"
].joined(separator: "\n")
}
}
Expand Down
33 changes: 32 additions & 1 deletion Loop/Managers/ProfileExpirationAlerter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import LoopCore
class ProfileExpirationAlerter {

static let expirationAlertWindow: TimeInterval = .days(20)
static let settingsPageExpirationWarningModeWindow: TimeInterval = .days(3)

static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) {

Expand All @@ -40,9 +41,11 @@ class ProfileExpirationAlerter {
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: 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!),
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
Expand All @@ -52,4 +55,32 @@ class ProfileExpirationAlerter {

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;
}
}
37 changes: 37 additions & 0 deletions Loop/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public struct SettingsView: View {
servicesSection
}
supportSection
if let profileExpiration = Bundle.main.profileExpiration, FeatureFlags.profileExpirationSettingsViewEnabled {
profileExpirationSection(profileExpiration: profileExpiration)
}
}
.insetGroupedListStyle()
.navigationBarTitle(Text(NSLocalizedString("Settings", comment: "Settings screen title")))
Expand Down Expand Up @@ -335,6 +338,40 @@ 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)

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"))
}
}
}

private static var dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
return dateFormatter // formats date like "February 4, 2023 at 2:35 PM"
}()

private var plusImage: some View {
Image(systemName: "plus.circle")
Expand Down