From 637a0354c5d66fa2b5f33d16e15a4a5cbe87852c Mon Sep 17 00:00:00 2001 From: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com> Date: Sat, 20 May 2023 14:23:25 -0700 Subject: [PATCH] feat: view to enable/disable Unstable Features --- AltStore.xcodeproj/project.pbxproj | 12 ++--- AltStore/AppDelegate.swift | 2 + .../Resources/en.lproj/Localizable.strings | 3 ++ .../SwiftUI/Generated/Localizations.swift | 4 ++ .../SwiftUI/Views/Settings/DevModeView.swift | 9 ++++ .../SwiftUI/Views/Settings/SettingsView.swift | 14 ++++-- .../Views/Settings/UnstableFeaturesView.swift | 35 ++++++++++++++ .../Unstable Features/UnstableFeatures.swift | 47 +++++++------------ 8 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 AltStore/SwiftUI/Views/Settings/UnstableFeaturesView.swift diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index c76965ad..cd01ba05 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ 4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; }; 990D2AE22A1910CD0055D93C /* UnstableFeatures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990D2AE12A1910CD0055D93C /* UnstableFeatures.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 */; }; 992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; }; 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 = ""; }; 990D2AE12A1910CD0055D93C /* UnstableFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeatures.swift; sourceTree = ""; }; 990D2AEF2A192E060055D93C /* UIApplication+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIApplication+Alert.swift"; sourceTree = ""; }; + 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnstableFeaturesView.swift; sourceTree = ""; }; 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = ""; }; 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 = ""; }; @@ -1180,7 +1182,6 @@ isa = PBXGroup; children = ( 1FAFC5B52927E06300B8D837 /* RootView.swift */, - 1FAFC5B12927E02E00B8D837 /* Authentication */, 1F981B0F29AA0F9B0014950E /* Onboarding */, 1FAFC5B22927E03300B8D837 /* News */, 1FAFC5B32927E03D00B8D837 /* Browse */, @@ -1191,13 +1192,6 @@ path = Views; sourceTree = ""; }; - 1FAFC5B12927E02E00B8D837 /* Authentication */ = { - isa = PBXGroup; - children = ( - ); - path = Authentication; - sourceTree = ""; - }; 1FAFC5B22927E03300B8D837 /* News */ = { isa = PBXGroup; children = ( @@ -1263,6 +1257,7 @@ 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */, 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */, 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */, + 990D2AFF2A19593F0055D93C /* UnstableFeaturesView.swift */, ); path = Settings; sourceTree = ""; @@ -2962,6 +2957,7 @@ 1F0DD8452936B3FE007608A4 /* FilledButtonStyle.swift in Sources */, BF8F69C222E659F700049BA1 /* AppContentViewController.swift in Sources */, BF08858522DE7EC800DE9F1E /* UpdateCollectionViewCell.swift in Sources */, + 990D2B002A19593F0055D93C /* UnstableFeaturesView.swift in Sources */, BFB39B5C252BC10E00D1BE50 /* Managed.swift in Sources */, BF770E5822BC3D0F002A40FE /* RefreshGroup.swift in Sources */, 19B9B7452845E6DF0076EF69 /* SelectTeamViewController.swift in Sources */, diff --git a/AltStore/AppDelegate.swift b/AltStore/AppDelegate.swift index cb017fe1..b7178e1f 100644 --- a/AltStore/AppDelegate.swift +++ b/AltStore/AppDelegate.swift @@ -65,7 +65,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { // Register default settings before doing anything else. UserDefaults.registerDefaults() + #if UNSTABLE UnstableFeatures.load() + #endif LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported diff --git a/AltStore/Resources/en.lproj/Localizable.strings b/AltStore/Resources/en.lproj/Localizable.strings index ff089400..c8b0f81a 100644 --- a/AltStore/Resources/en.lproj/Localizable.strings +++ b/AltStore/Resources/en.lproj/Localizable.strings @@ -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.DangerZone.usePreferred" = "Use preferred servers"; "AdvancedSettingsView.DangerZone.anisetteURL" = "Anisette URL"; + +/* UnstableFeaturesView */ +"UnstableFeaturesView.title" = "Unstable Features"; diff --git a/AltStore/SwiftUI/Generated/Localizations.swift b/AltStore/SwiftUI/Generated/Localizations.swift index 84d72524..cd6ef1c9 100644 --- a/AltStore/SwiftUI/Generated/Localizations.swift +++ b/AltStore/SwiftUI/Generated/Localizations.swift @@ -405,6 +405,10 @@ internal enum L10n { /// 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 nesting type_body_length type_name vertical_whitespace_opening_braces diff --git a/AltStore/SwiftUI/Views/Settings/DevModeView.swift b/AltStore/SwiftUI/Views/Settings/DevModeView.swift index 0b074885..b3b28419 100644 --- a/AltStore/SwiftUI/Views/Settings/DevModeView.swift +++ b/AltStore/SwiftUI/Views/Settings/DevModeView.swift @@ -136,6 +136,15 @@ struct DevModeMenu: View { LCManager.shared.isVisible = value } + NavigationLink(L10n.UnstableFeaturesView.title) { + #if UNSTABLE + UnstableFeaturesView(allowDevModeOnlyFeatures: true) + #endif + } + #if !UNSTABLE + .disabled(true) + #endif + NavigationLink(L10n.DevModeView.dataExplorer) { FileExplorer.normal(url: FileManager.default.altstoreSharedDirectory) .navigationTitle(L10n.DevModeView.dataExplorer) diff --git a/AltStore/SwiftUI/Views/Settings/SettingsView.swift b/AltStore/SwiftUI/Views/Settings/SettingsView.swift index 94b12307..caad2054 100644 --- a/AltStore/SwiftUI/Views/Settings/SettingsView.swift +++ b/AltStore/SwiftUI/Views/Settings/SettingsView.swift @@ -113,6 +113,10 @@ struct SettingsView: View { SiriShortcutSetupView(shortcut: shortcut) } } + + NavigationLink("Show Refresh Attempts") { + RefreshAttemptsView() + } } header: { Text(L10n.SettingsView.refreshingApps) } footer: { @@ -162,15 +166,17 @@ struct SettingsView: View { NavigationLink("Show Error Log") { ErrorLogView() } - - NavigationLink("Show Refresh Attempts") { - RefreshAttemptsView() - } NavigationLink(L10n.AdvancedSettingsView.title) { AdvancedSettingsView() } + #if UNSTABLE + NavigationLink(L10n.UnstableFeaturesView.title) { + UnstableFeaturesView(allowDevModeOnlyFeatures: false) + } + #endif + Toggle(L10n.SettingsView.debugLogging, isOn: self.$isDebugLoggingEnabled) .onChange(of: self.isDebugLoggingEnabled) { value in UserDefaults.shared.isDebugLoggingEnabled = value diff --git a/AltStore/SwiftUI/Views/Settings/UnstableFeaturesView.swift b/AltStore/SwiftUI/Views/Settings/UnstableFeaturesView.swift new file mode 100644 index 00000000..f4383dfc --- /dev/null +++ b/AltStore/SwiftUI/Views/Settings/UnstableFeaturesView.swift @@ -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 diff --git a/AltStore/Unstable Features/UnstableFeatures.swift b/AltStore/Unstable Features/UnstableFeatures.swift index 69d82290..641df5bf 100644 --- a/AltStore/Unstable Features/UnstableFeatures.swift +++ b/AltStore/Unstable Features/UnstableFeatures.swift @@ -6,6 +6,8 @@ // 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 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 @@ -29,71 +31,58 @@ enum AvailableUnstableFeature: String, CaseIterable { } } -class UnstableFeatures { + +class UnstableFeatures: ObservableObject { #if UNSTABLE - private static var features: [AvailableUnstableFeature: Bool] = [:] - #endif + static let shared = UnstableFeatures() + @Published var features: [AvailableUnstableFeature: Bool] = [:] static func load() { - #if UNSTABLE - - if features.count > 0 { return print("It seems unstable features have already been loaded, skipping") } + if shared.features.count > 0 { return print("It seems unstable features have already been loaded, skipping") } if let rawFeatures = UserDefaults.shared.unstableFeatures, let rawFeatures = try? JSONDecoder().decode([String: Bool].self, from: rawFeatures) { for rawFeature in rawFeatures { if let feature = AvailableUnstableFeature.allCases.first(where: { feature in String(describing: feature) == rawFeature.key }) { - features[feature] = rawFeature.value + shared.features[feature] = rawFeature.value } else { 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) } 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)") for feature in AvailableUnstableFeature.allCases { - features[feature] = false + shared.features[feature] = false } save() } - - #else - print("Unstable features are not available on this build") - #endif } private static func save(load: Bool = false) { - #if UNSTABLE - var rawFeatures: [String: Bool] = [:] - for feature in features { + for feature in shared.features { rawFeatures[String(describing: feature.key)] = feature.value } UserDefaults.shared.unstableFeatures = try! JSONEncoder().encode(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) { - #if UNSTABLE - - features[feature] = enabled + shared.features[feature] = enabled 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 static func enabled(_ feature: AvailableUnstableFeature) -> Bool { #if UNSTABLE - features[feature] ?? false + shared.features[feature] ?? false #else false #endif