Skip to content
Open
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
9 changes: 9 additions & 0 deletions Common/FeatureFlags.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct FeatureFlagConfiguration: Decodable {
let profileExpirationSettingsViewEnabled: Bool
let missedMealNotifications: Bool
let allowAlgorithmExperiments: Bool
let devBranchWarningEnabled: Bool


fileprivate init() {
Expand Down Expand Up @@ -232,6 +233,13 @@ struct FeatureFlagConfiguration: Decodable {
#else
self.allowAlgorithmExperiments = false
#endif

// Swift compiler config is inverse, since the default state is enabled.
#if DEV_BRANCH_WARNING_DISABLED
self.devBranchWarningEnabled = false
#else
self.devBranchWarningEnabled = true
#endif
}
}

Expand Down Expand Up @@ -267,6 +275,7 @@ extension FeatureFlagConfiguration : CustomDebugStringConvertible {
"* profileExpirationSettingsViewEnabled: \(profileExpirationSettingsViewEnabled)",
"* missedMealNotifications: \(missedMealNotifications)",
"* allowAlgorithmExperiments: \(allowAlgorithmExperiments)",
"* devBranchWarningEnabled: \(devBranchWarningEnabled)",
"* allowExperimentalFeatures: \(allowExperimentalFeatures)"
].joined(separator: "\n")
}
Expand Down
4 changes: 4 additions & 0 deletions Loop.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,7 @@
C1F00C60285A802A006302C5 /* SwiftCharts in Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; };
C1F00C78285A8256006302C5 /* SwiftCharts in Embed Frameworks */ = {isa = PBXBuildFile; productRef = C1F00C5F285A802A006302C5 /* SwiftCharts */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */; };
FADE0001000000000000DE02 /* DevelopmentBranchAlerter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FADE0001000000000000DE01 /* DevelopmentBranchAlerter.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 */; };
Expand Down Expand Up @@ -1316,6 +1317,7 @@
C1EE9E802A38D0FB0064784A /* BuildDetails.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = BuildDetails.plist; sourceTree = "<group>"; };
C1EF747128D6A44A00C8C083 /* CrashRecoveryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashRecoveryManager.swift; sourceTree = "<group>"; };
C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppExpirationAlerter.swift; sourceTree = "<group>"; };
FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevelopmentBranchAlerter.swift; sourceTree = "<group>"; };
C1F48FF62995821600C8BD69 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = "<group>"; };
C1F7822527CC056900C0919A /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; };
C1F8B1D122375E4200DD66CF /* BolusProgressTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusProgressTableViewCell.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2037,6 +2039,7 @@
E9B355232935906B0076AB04 /* Missed Meal Detection */,
3ED319902EB65A2D00820BCF /* Live Activity */,
C1F2075B26D6F9B0007AB7EB /* AppExpirationAlerter.swift */,
FADE0001000000000000DE01 /* DevelopmentBranchAlerter.swift */,
A96DAC2B2838F31200D94E38 /* SharedLogging.swift */,
7E69CFFB2A16A77E00203CBD /* ResetLoopManager.swift */,
84AA81E42A4A3981000B658B /* DeeplinkManager.swift */,
Expand Down Expand Up @@ -3391,6 +3394,7 @@
3ED319992EB65A6900820BCF /* LiveActivityManagementView.swift in Sources */,
3ED3199A2EB65A6900820BCF /* LiveActivityBottomRowManagerView.swift in Sources */,
C1F2075C26D6F9B0007AB7EB /* AppExpirationAlerter.swift in Sources */,
FADE0001000000000000DE02 /* DevelopmentBranchAlerter.swift in Sources */,
B4FEEF7D24B8A71F00A8DF9B /* DeviceDataManager+DeviceStatus.swift in Sources */,
142CB7592A60BF2E0075748A /* EditMode.swift in Sources */,
E95D380324EADF36005E2F50 /* CarbStoreProtocol.swift in Sources */,
Expand Down
50 changes: 50 additions & 0 deletions Loop/Managers/DevelopmentBranchAlerter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// DevelopmentBranchAlerter.swift
// Loop
//
// Copyright © 2026 LoopKit Authors. All rights reserved.
//

import UIKit

enum DevelopmentBranchAlerter {

// The LoopWorkspace superproject branch this warning applies to.
private static let developmentBranchName = "dev"

private static let switchToMainURL = URL(string: "https://loopkit.github.io/loopdocs/browser/build-dev-browser/#build-development-version")!

/// Presents a blocking warning when this is a build from the development branch.
/// Shown on every launch; the alert can only be dismissed by an explicit choice.
static func alertIfNeeded(viewControllerToPresentFrom: UIViewController) {
guard FeatureFlags.devBranchWarningEnabled else {
return
}

guard BuildDetails.default.workspaceGitBranch == developmentBranchName else {
return
}

let alert = UIAlertController(
title: NSLocalizedString("Warning", comment: "Title of the warning shown at launch on development builds"),
message: NSLocalizedString("This is the development version of Loop, built from the dev branch. Any updates on this branch may contain new, untested features, and may be unsafe. If you are not a tester, please do not use this branch, and switch to main.", comment: "Body of the warning shown at launch on development builds"),
preferredStyle: .alert
)

alert.addAction(UIAlertAction(
title: NSLocalizedString("I'm a tester", comment: "Button that dismisses the development build warning"),
style: .default,
handler: nil
))

alert.addAction(UIAlertAction(
title: NSLocalizedString("Switch to main", comment: "Button on the development build warning that opens documentation about switching branches"),
style: .default,
handler: { _ in
UIApplication.shared.open(switchToMainURL)
}
))

viewControllerToPresentFrom.present(alert, animated: true)
}
}
4 changes: 4 additions & 0 deletions Loop/Managers/LoopAppManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,10 @@ class LoopAppManager: NSObject {
self.state = state.next

alertManager.playbackAlertsFromPersistence()

if let rootViewController = rootViewController {
DevelopmentBranchAlerter.alertIfNeeded(viewControllerToPresentFrom: rootViewController)
}
}

// MARK: - Life Cycle
Expand Down