feat: view to enable/disable Unstable Features

This commit is contained in:
naturecodevoid
2023-05-20 14:23:25 -07:00
parent 9c3461b0c6
commit 637a0354c5
8 changed files with 85 additions and 41 deletions

View File

@@ -88,6 +88,7 @@
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */; }; 990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */; };
990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */; }; 990D2AF02A192E060055D93C /* UIApplication+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */; };
990D2B002A19593F0055D93C /* UnstableFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */; };
9922FFEC29B501C50020F868 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 9922FFEB29B501C50020F868 /* Starscream */; }; 9922FFEC29B501C50020F868 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 9922FFEB29B501C50020F868 /* Starscream */; };
992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; }; 992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; };
994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; }; 994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; };
@@ -655,6 +656,7 @@
1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; }; 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; };
990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeatures.swift; sourceTree = "<group>"; }; 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeatures.swift; sourceTree = "<group>"; };
990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Alert.swift"; sourceTree = "<group>"; }; 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Alert.swift"; sourceTree = "<group>"; };
990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeaturesView.swift; sourceTree = "<group>"; };
994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = "<group>"; }; 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = "<group>"; };
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; }; 9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = "<group>"; }; 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = "<group>"; };
@@ -1180,7 +1182,6 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
1FAFC5B52927E06300B8D837 /* RootView.swift */, 1FAFC5B52927E06300B8D837 /* RootView.swift */,
1FAFC5B12927E02E00B8D837 /* Authentication */,
1F981B0F29AA0F9B0014950E /* Onboarding */, 1F981B0F29AA0F9B0014950E /* Onboarding */,
1FAFC5B22927E03300B8D837 /* News */, 1FAFC5B22927E03300B8D837 /* News */,
1FAFC5B32927E03D00B8D837 /* Browse */, 1FAFC5B32927E03D00B8D837 /* Browse */,
@@ -1191,13 +1192,6 @@
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
1FAFC5B12927E02E00B8D837 /* Authentication */ = {
isa = PBXGroup;
children = (
);
path = Authentication;
sourceTree = "<group>";
};
1FAFC5B22927E03300B8D837 /* News */ = { 1FAFC5B22927E03300B8D837 /* News */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1263,6 +1257,7 @@
99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */, 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */,
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */, 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */,
99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */, 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */,
990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */,
); );
path = Settings; path = Settings;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -2962,6 +2957,7 @@
1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */, 1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */,
BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */,
BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */, BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */,
990D2B002A19593F0055D93C /* UnstableFeaturesView.swift in Sources */,
BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */, BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */,
BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */, BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */,
19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */, 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */,

View File

@@ -65,7 +65,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
// Register default settings before doing anything else. // Register default settings before doing anything else.
UserDefaults.registerDefaults() UserDefaults.registerDefaults()
#if UNSTABLE
UnstableFeatures.load() UnstableFeatures.load()
#endif
LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled
LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported

View File

@@ -209,3 +209,6 @@ You should only enable Developer Mode if you meet one of the following requireme
"AdvancedSettingsView.dangerZoneInfo" = "If you disable \"Use preferred servers\" then SideStore will use the server you input into the \"Anisette URL\" box rather than one selected in \"Anisette Server\"."; "AdvancedSettingsView.dangerZoneInfo" = "If you disable \"Use preferred servers\" then SideStore will use the server you input into the \"Anisette URL\" box rather than one selected in \"Anisette Server\".";
"AdvancedSettingsView.DangerZone.usePreferred" = "Use preferred servers"; "AdvancedSettingsView.DangerZone.usePreferred" = "Use preferred servers";
"AdvancedSettingsView.DangerZone.anisetteURL" = "Anisette URL"; "AdvancedSettingsView.DangerZone.anisetteURL" = "Anisette URL";
/* UnstableFeaturesView */
"UnstableFeaturesView.title" = "Unstable Features";

View File

@@ -405,6 +405,10 @@ internal enum L10n {
/// Trusted Sources /// Trusted Sources
internal static let trustedSources = L10n.tr("Localizable", "SourcesView.trustedSources", fallback: "Trusted Sources") internal static let trustedSources = L10n.tr("Localizable", "SourcesView.trustedSources", fallback: "Trusted Sources")
} }
internal enum UnstableFeaturesView {
/// UnstableFeaturesView
internal static let title = L10n.tr("Localizable", "UnstableFeaturesView.title", fallback: "Unstable Features")
}
} }
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces // swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces

View File

@@ -136,6 +136,15 @@ struct DevModeMenu: View {
LCManager.shared.isVisible = value LCManager.shared.isVisible = value
} }
NavigationLink(L10n.UnstableFeaturesView.title) {
#if UNSTABLE
UnstableFeaturesView(allowDevModeOnlyFeatures: true)
#endif
}
#if !UNSTABLE
.disabled(true)
#endif
NavigationLink(L10n.DevModeView.dataExplorer) { NavigationLink(L10n.DevModeView.dataExplorer) {
FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory) FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory)
.navigationTitle(L10n.DevModeView.dataExplorer) .navigationTitle(L10n.DevModeView.dataExplorer)

View File

@@ -113,6 +113,10 @@ struct SettingsView: View {
SiriShortcutSetupView(shortcut: shortcut) SiriShortcutSetupView(shortcut: shortcut)
} }
} }
NavigationLink("Show Refresh Attempts") {
RefreshAttemptsView()
}
} header: { } header: {
Text(L10n.SettingsView.refreshingApps) Text(L10n.SettingsView.refreshingApps)
} footer: { } footer: {
@@ -163,14 +167,16 @@ struct SettingsView: View {
ErrorLogView() ErrorLogView()
} }
NavigationLink("Show Refresh Attempts") {
RefreshAttemptsView()
}
NavigationLink(L10n.AdvancedSettingsView.title) { NavigationLink(L10n.AdvancedSettingsView.title) {
AdvancedSettingsView() AdvancedSettingsView()
} }
#if UNSTABLE
NavigationLink(L10n.UnstableFeaturesView.title) {
UnstableFeaturesView(allowDevModeOnlyFeatures: false)
}
#endif
Toggle(L10n.SettingsView.debugLogging, isOn: self.$isDebugLoggingEnabled) Toggle(L10n.SettingsView.debugLogging, isOn: self.$isDebugLoggingEnabled)
.onChange(of: self.isDebugLoggingEnabled) { value in .onChange(of: self.isDebugLoggingEnabled) { value in
UserDefaults.shared.isDebugLoggingEnabled = value UserDefaults.shared.isDebugLoggingEnabled = value

View File

@@ -0,0 +1,35 @@
//
// UnstableFeaturesView.swift
// SideStore
//
// Created by naturecodevoid on 5/20/23.
// Copyright © 2023 SideStore. All rights reserved.
//
#if UNSTABLE
import SwiftUI
struct UnstableFeaturesView: View {
@ObservedObject private var shared = UnstableFeatures.shared
var allowDevModeOnlyFeatures: Bool
var body: some View {
List {
ForEach(shared.features.filter { feature, _ in feature != AvailableUnstableFeature.dummy && allowDevModeOnlyFeatures ? true : feature.availableOutsideDevMode() }.sorted(by: { _, _ in true }), id: \.key) { feature, _ in
Toggle(isOn: Binding(get: { UnstableFeatures.enabled(feature) }, set: { newValue in UnstableFeatures.set(feature, enabled: newValue) })) {
Text(String(describing: feature))
let link = "https://github.com/SideStore/SideStore/issues/\(feature.rawValue)"
Link(link, destination: URL(string: link)!)
}
}
}.navigationTitle(L10n.UnstableFeaturesView.title)
}
}
struct UnstableFeaturesView_Previews: PreviewProvider {
static var previews: some View {
UnstableFeaturesView(allowDevModeOnlyFeatures: true)
}
}
#endif

View File

@@ -6,6 +6,8 @@
// Copyright © 2023 SideStore. All rights reserved. // Copyright © 2023 SideStore. All rights reserved.
// //
import SwiftUI
// I prefixed it with Available to make UnstableFeatures come up first in autocomplete, feel free to rename it if you know a better name // I prefixed it with Available to make UnstableFeatures come up first in autocomplete, feel free to rename it if you know a better name
enum AvailableUnstableFeature: String, CaseIterable { enum AvailableUnstableFeature: String, CaseIterable {
// The value will be the GitHub Issue number. For example, "123" would correspond to https://github.com/SideStore/SideStore/issues/123 // The value will be the GitHub Issue number. For example, "123" would correspond to https://github.com/SideStore/SideStore/issues/123
@@ -29,71 +31,58 @@ enum AvailableUnstableFeature: String, CaseIterable {
} }
} }
class UnstableFeatures {
class UnstableFeatures: ObservableObject {
#if UNSTABLE #if UNSTABLE
private static var features: [AvailableUnstableFeature: Bool] = [:] static let shared = UnstableFeatures()
#endif @Published var features: [AvailableUnstableFeature: Bool] = [:]
static func load() { static func load() {
#if UNSTABLE if shared.features.count > 0 { return print("It seems unstable features have already been loaded, skipping") }
if features.count > 0 { return print("It seems unstable features have already been loaded, skipping") }
if let rawFeatures = UserDefaults.shared.unstableFeatures, if let rawFeatures = UserDefaults.shared.unstableFeatures,
let rawFeatures = try? JSONDecoder().decode([String: Bool].self, from: rawFeatures) { let rawFeatures = try? JSONDecoder().decode([String: Bool].self, from: rawFeatures) {
for rawFeature in rawFeatures { for rawFeature in rawFeatures {
if let feature = AvailableUnstableFeature.allCases.first(where: { feature in String(describing: feature) == rawFeature.key }) { if let feature = AvailableUnstableFeature.allCases.first(where: { feature in String(describing: feature) == rawFeature.key }) {
features[feature] = rawFeature.value shared.features[feature] = rawFeature.value
} else { } else {
print("Unknown unstable feature: \(rawFeature.key) = \(rawFeature.value)") print("Unknown unstable feature: \(rawFeature.key) = \(rawFeature.value)")
} }
} }
for feature in AvailableUnstableFeature.allCases {
if shared.features[feature] == nil {
shared.features[feature] = false
}
}
save(load: true) save(load: true)
} else { } else {
print("Setting all unstable features to false since we couldn't load them from UserDefaults (either they were never saved or there was an error decoding JSON)") print("Setting all unstable features to false since we couldn't load them from UserDefaults (either they were never saved or there was an error decoding JSON)")
for feature in AvailableUnstableFeature.allCases { for feature in AvailableUnstableFeature.allCases {
features[feature] = false shared.features[feature] = false
} }
save() save()
} }
#else
print("Unstable features are not available on this build")
#endif
} }
private static func save(load: Bool = false) { private static func save(load: Bool = false) {
#if UNSTABLE
var rawFeatures: [String: Bool] = [:] var rawFeatures: [String: Bool] = [:]
for feature in features { for feature in shared.features {
rawFeatures[String(describing: feature.key)] = feature.value rawFeatures[String(describing: feature.key)] = feature.value
} }
UserDefaults.shared.unstableFeatures = try! JSONEncoder().encode(rawFeatures) UserDefaults.shared.unstableFeatures = try! JSONEncoder().encode(rawFeatures)
print("\(load ? "Loaded" : "Saved") unstable features: \(String(describing: rawFeatures))") print("\(load ? "Loaded" : "Saved") unstable features: \(String(describing: rawFeatures))")
#else
// we want this to crash, this function should never be triggered on non-unstable builds
fatalError("Tried to save unstable features on non-unstable build!")
#endif
} }
static func set(_ feature: AvailableUnstableFeature, enabled: Bool) { static func set(_ feature: AvailableUnstableFeature, enabled: Bool) {
#if UNSTABLE shared.features[feature] = enabled
features[feature] = enabled
save() save()
#else
// we want this to crash, this function should never be triggered on non-unstable builds
fatalError("Tried to set unstable feature \(String(describing: feature)) to \(enabled) on non-unstable build!")
#endif
} }
#endif
@inline(__always) // hopefully this will help the compiler realize that if statements that use this function should be removed on non-unstable builds @inline(__always) // hopefully this will help the compiler realize that if statements that use this function should be removed on non-unstable builds
static func enabled(_ feature: AvailableUnstableFeature) -> Bool { static func enabled(_ feature: AvailableUnstableFeature) -> Bool {
#if UNSTABLE #if UNSTABLE
features[feature] ?? false shared.features[feature] ?? false
#else #else
false false
#endif #endif