From d45803a7cc72c357ad3832d8acd6b62374fe4066 Mon Sep 17 00:00:00 2001
From: mahee96 <47920326+mahee96@users.noreply.github.com>
Date: Sun, 22 Feb 2026 07:25:07 +0530
Subject: [PATCH] settings: split out beta testing section so that users can
enable it confidently as they see
---
AltStore/Settings/Settings.storyboard | 328 +++++++++---------
.../Settings/SettingsViewController.swift | 79 +++--
2 files changed, 210 insertions(+), 197 deletions(-)
diff --git a/AltStore/Settings/Settings.storyboard b/AltStore/Settings/Settings.storyboard
index fafa2eee..f14d7c1a 100644
--- a/AltStore/Settings/Settings.storyboard
+++ b/AltStore/Settings/Settings.storyboard
@@ -22,7 +22,7 @@
-
+
-
+
@@ -1439,7 +1443,7 @@
-
+
@@ -1452,7 +1456,7 @@
-
+
@@ -1487,9 +1491,9 @@
-
-
-
+
+
+
diff --git a/AltStore/Settings/SettingsViewController.swift b/AltStore/Settings/SettingsViewController.swift
index 0ce2082a..617872d4 100644
--- a/AltStore/Settings/SettingsViewController.swift
+++ b/AltStore/Settings/SettingsViewController.swift
@@ -21,7 +21,7 @@ import UniformTypeIdentifiers
extension SettingsViewController
{
- fileprivate enum Section: Int, CaseIterable
+ private enum Section: Int, CaseIterable
{
case signIn
case account
@@ -31,14 +31,14 @@ extension SettingsViewController
case instructions
case techyThings
case credits
+ case betaTesting
case advancedSettings
case signing
- // diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
- case diagnostics
+ case diagnostics // diagnostics section, will be enabled on release builds only on swipe down with 3 fingers 3 times
// case macDirtyCow
}
- fileprivate enum AppRefreshRow: Int, CaseIterable
+ private enum AppRefreshRow: Int, CaseIterable
{
case backgroundRefresh
case noIdleTimeout
@@ -57,7 +57,7 @@ extension SettingsViewController
}
}
- fileprivate enum CreditsRow: Int, CaseIterable
+ private enum CreditsRow: Int, CaseIterable
{
case developer
case operations
@@ -65,33 +65,35 @@ extension SettingsViewController
case softwareLicenses
}
- fileprivate enum TechyThingsRow: Int, CaseIterable
+ private enum TechyThingsRow: Int, CaseIterable
{
case errorLog
case clearCache
}
- fileprivate enum AdvancedSettingsRow: Int, CaseIterable
+ private enum AdvancedSettingsRow: Int, CaseIterable
{
case sendFeedback
case refreshAttempts
case refreshSideJITServer
case resetPairingFile
case anisetteServers
- case betaUpdates
- case betaTrack
case customizeAppId
-// case hiddenSettings
}
- fileprivate enum SigningSettingsRow: Int, CaseIterable {
+ private enum SigningSettingsRow: Int, CaseIterable {
case importAccount
case exportAccount
case importCert
case exportCert
}
- fileprivate enum DiagnosticsRow: Int, CaseIterable
+ private enum BetaTestingRow: Int, CaseIterable {
+ case betaUpdates
+ case betaTrack
+ }
+
+ private enum DiagnosticsRow: Int, CaseIterable
{
case responseCaching
case exportResignedApp
@@ -476,8 +478,10 @@ private extension SettingsViewController
self.disableAppLimitSwitch.isOn = UserDefaults.standard.isAppLimitDisabled
// AdvancedSettingsRow
- self.betaUpdatesSwitch.isOn = UserDefaults.standard.isBetaUpdatesEnabled
self.customizeAppIdSwitch.isOn = UserDefaults.standard.customizeAppId
+
+ // BetaTestingRow
+ self.betaUpdatesSwitch.isOn = UserDefaults.standard.isBetaUpdatesEnabled
self.betaTrackPopupButton.isEnabled = UserDefaults.standard.isBetaUpdatesEnabled
// DiagnosticsRow
@@ -494,7 +498,7 @@ private extension SettingsViewController
}
}
- func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool)
+ private func prepare(_ settingsHeaderFooterView: SettingsHeaderFooterView, for section: Section, isHeader: Bool)
{
settingsHeaderFooterView.primaryLabel.isHidden = !isHeader
settingsHeaderFooterView.secondaryLabel.isHidden = isHeader
@@ -572,7 +576,6 @@ private extension SettingsViewController
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("ADVANCED SETTINGS", comment: "")
case .signing:
- // FIXME: Why "Enable Background Refresh ..." appear here if secondaryLabel is not specified???
if isHeader
{
settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("SIGNING", comment: "")
@@ -581,6 +584,23 @@ private extension SettingsViewController
{
settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString("", comment: "")
}
+
+ case .betaTesting:
+ if isHeader
+ {
+ settingsHeaderFooterView.primaryLabel.text = NSLocalizedString("BETA TESTING", comment: "")
+ }
+ else
+ {
+ settingsHeaderFooterView.secondaryLabel.text = NSLocalizedString(
+ """
+ Opt in for beta testing to receive regular updates and early previews of upcoming releases.\n
+ Please note that these builds are experimental and may be unstable or break unexpectedly.
+ """,
+ comment: ""
+ )
+ }
+
case .diagnostics:
@@ -599,7 +619,7 @@ private extension SettingsViewController
}
}
- func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat
+ private func preferredHeight(for settingsHeaderFooterView: SettingsHeaderFooterView, in section: Section, isHeader: Bool) -> CGFloat
{
let widthConstraint = settingsHeaderFooterView.contentView.widthAnchor.constraint(equalToConstant: tableView.bounds.width)
NSLayoutConstraint.activate([widthConstraint])
@@ -611,7 +631,7 @@ private extension SettingsViewController
return size.height
}
- func isSectionHidden(_ section: Section) -> Bool
+ private func isSectionHidden(_ section: Section) -> Bool
{
switch section
{
@@ -1033,7 +1053,7 @@ extension SettingsViewController
case _ where isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil
case .account where self.activeTeam == nil: return nil
- case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing ,.diagnostics /* ,.macDirtyCow */:
+ case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics /* ,.macDirtyCow */:
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(headerView, for: section, isHeader: true)
return headerView
@@ -1050,7 +1070,7 @@ extension SettingsViewController
case _ where isSectionHidden(section): return nil
case .signIn where self.activeTeam != nil: return nil
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
- case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing:
+ case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .betaTesting:
let footerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterView") as! SettingsHeaderFooterView
self.prepare(footerView, for: section, isHeader: false)
return footerView
@@ -1067,8 +1087,7 @@ extension SettingsViewController
case _ where isSectionHidden(section): return 1.0
case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0
- // case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .macDirtyCow, .advanced:
- case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .diagnostics:
+ case .signIn, .account, .patreon, .display, .appRefresh, .techyThings, .credits, .advancedSettings, .signing, .betaTesting, .diagnostics:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: true)
return height
@@ -1085,7 +1104,7 @@ extension SettingsViewController
case .signIn where self.activeTeam != nil: return 1.0
case .account where self.activeTeam == nil: return 1.0
// case .signIn, .patreon, .display, .appRefresh, .techyThings, .macDirtyCow:
- case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .diagnostics:
+ case .signIn, .patreon, .display, .appRefresh, .techyThings, .signing, .diagnostics, .betaTesting:
let height = self.preferredHeight(for: self.prototypeHeaderFooterView, in: section, isHeader: false)
return height
@@ -1348,17 +1367,7 @@ extension SettingsViewController
let anisetteServersController = UIHostingController(rootView: anisetteServersView)
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil)
-
-// case .hiddenSettings:
-// // Create the URL that deep links to your app's custom settings.
-// if let url = URL(string: UIApplication.openSettingsURLString) {
-// // Ask the system to open that URL.
-// UIApplication.shared.open(url)
-// } else {
-// ELOG("UIApplication.openSettingsURLString invalid")
-// }
- case .refreshAttempts, .betaUpdates, .betaTrack, .customizeAppId: break
-
+ case .refreshAttempts, .customizeAppId: break
}
case .signing:
let row = SigningSettingsRow.allCases[indexPath.row]
@@ -1515,7 +1524,7 @@ extension SettingsViewController
self.present(exportVC, animated: true)
}
}
-
+
case .diagnostics:
let row = DiagnosticsRow.allCases[indexPath.row]
switch row {
@@ -1567,7 +1576,7 @@ extension SettingsViewController
// case .account, .patreon, .display, .instructions, .macDirtyCow: break
- case .account, .patreon, .display, .instructions: break
+ case .account, .patreon, .display, .instructions, .betaTesting: break
}