More improvements to unstable features and advanced settings

- added description of what they are and notice if there are none available
- move them to advanced settings
- add alert for unstable features in dev mode if the build does not have them enabled
- move stuff out of the danger zone and into anisette section in advanced settings
This commit is contained in:
naturecodevoid
2023-05-24 21:01:11 -07:00
parent a8917f095e
commit 2219035cd0
7 changed files with 107 additions and 47 deletions

View File

@@ -198,17 +198,25 @@ You should only enable Developer Mode if you meet one of the following requireme
- It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it - It is currently limited to a maximum depth of 3 to ensure it doesn't take too long to iterate over everything when you open it
- Very buggy - Very buggy
- There are multiple unimplemented actions"; - There are multiple unimplemented actions";
"DevModeView.unstableFeaturesNightlyOnly" = "Unstable Features are only available on nightly builds or debug builds";
/* AsyncFallibleButton */ /* AsyncFallibleButton */
"AsyncFallibleButton.error" = "An error occurred"; "AsyncFallibleButton.error" = "An error occurred";
/* AdvancedSettingsView */ /* AdvancedSettingsView */
"AdvancedSettingsView.title" = "Advanced Settings"; "AdvancedSettingsView.title" = "Advanced Settings";
"AdvancedSettingsView.anisette" = "Anisette Server"; "AdvancedSettingsView.anisetteSettings" = "Anisette";
"AdvancedSettingsView.AnisetteSettings.server" = "Anisette Server";
"AdvancedSettingsView.AnisetteSettings.usePreferred" = "Use preferred servers";
"AdvancedSettingsView.AnisetteSettings.anisetteURL" = "Anisette URL";
"AdvancedSettingsView.AnisetteSettings.footer" = "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" = "Danger Zone"; "AdvancedSettingsView.dangerZone" = "Danger Zone";
"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 */
"UnstableFeaturesView.title" = "Unstable Features"; "UnstableFeaturesView.title" = "Unstable Features";
"UnstableFeaturesView.description" = "Unstable Features are features that are currently being tested or still a work-in-progress and not ready for public usage. Because of this, they are only available on nightly builds. By default, all unstable features are off. Additionally, only more stable unstable features are available in Advanced Settings; most are locked behind Developer Mode to ensure normal users don't use them.
Every unstable feature has a tracking issue, which contains info on what the unstable feature adds and tracks the unstable feature status. To view a tracking issue for an unstable feature, simply click it in the list. **Please use the tracking issue for reporting bugs or giving feedback.**
**Do not ask for support on using unstable features, you will not receive any help.**";
"UnstableFeaturesView.noUnstableFeatures" = "There are currently no unstable features available.";

View File

@@ -37,19 +37,21 @@ internal enum L10n {
internal static let title = L10n.tr("Localizable", "AddSourceView.title", fallback: "Add Source") internal static let title = L10n.tr("Localizable", "AddSourceView.title", fallback: "Add Source")
} }
internal enum AdvancedSettingsView { internal enum AdvancedSettingsView {
/// Anisette Server /// Anisette
internal static let anisette = L10n.tr("Localizable", "AdvancedSettingsView.anisette", fallback: "Anisette Server") internal static let anisetteSettings = L10n.tr("Localizable", "AdvancedSettingsView.anisetteSettings", fallback: "Anisette")
/// Danger Zone /// Danger Zone
internal static let dangerZone = L10n.tr("Localizable", "AdvancedSettingsView.dangerZone", fallback: "Danger Zone") internal static let dangerZone = L10n.tr("Localizable", "AdvancedSettingsView.dangerZone", fallback: "Danger Zone")
/// 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".
internal static let dangerZoneInfo = L10n.tr("Localizable", "AdvancedSettingsView.dangerZoneInfo", fallback: "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 /// AdvancedSettingsView
internal static let title = L10n.tr("Localizable", "AdvancedSettingsView.title", fallback: "Advanced Settings") internal static let title = L10n.tr("Localizable", "AdvancedSettingsView.title", fallback: "Advanced Settings")
internal enum DangerZone { internal enum AnisetteSettings {
/// Anisette URL /// Anisette URL
internal static let anisetteURL = L10n.tr("Localizable", "AdvancedSettingsView.DangerZone.anisetteURL", fallback: "Anisette URL") internal static let anisetteURL = L10n.tr("Localizable", "AdvancedSettingsView.AnisetteSettings.anisetteURL", fallback: "Anisette URL")
/// 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".
internal static let footer = L10n.tr("Localizable", "AdvancedSettingsView.AnisetteSettings.footer", fallback: "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\".")
/// Anisette Server
internal static let server = L10n.tr("Localizable", "AdvancedSettingsView.AnisetteSettings.server", fallback: "Anisette Server")
/// Use preferred servers /// Use preferred servers
internal static let usePreferred = L10n.tr("Localizable", "AdvancedSettingsView.DangerZone.usePreferred", fallback: "Use preferred servers") internal static let usePreferred = L10n.tr("Localizable", "AdvancedSettingsView.AnisetteSettings.usePreferred", fallback: "Use preferred servers")
} }
} }
internal enum AppAction { internal enum AppAction {
@@ -281,6 +283,8 @@ internal enum L10n {
internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode") internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode")
/// Temporary File Explorer /// Temporary File Explorer
internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer") internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer")
/// Unstable Features are only available on nightly builds or debug builds
internal static let unstableFeaturesNightlyOnly = L10n.tr("Localizable", "DevModeView.unstableFeaturesNightlyOnly", fallback: "Unstable Features are only available on nightly builds or debug builds")
internal enum Minimuxer { internal enum Minimuxer {
/// AFC File Explorer (check footer for notes) /// AFC File Explorer (check footer for notes)
internal static let afcExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.afcExplorer", fallback: "AFC File Explorer (check footer for notes)") internal static let afcExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.afcExplorer", fallback: "AFC File Explorer (check footer for notes)")
@@ -406,6 +410,14 @@ internal enum L10n {
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 { internal enum UnstableFeaturesView {
/// Unstable Features are features that are currently being tested or still a work-in-progress and not ready for public usage. Because of this, they are only available on nightly builds. By default, all unstable features are off. Additionally, only more stable unstable features are available in Advanced Settings; most are locked behind Developer Mode to ensure normal users don't use them.
///
/// Every unstable feature has a tracking issue, which contains info on what the unstable feature adds and tracks the unstable feature status. To view a tracking issue for an unstable feature, simply click it in the list. **Please use the tracking issue for reporting bugs or giving feedback.**
///
/// **Do not ask for support on using unstable features, you will not receive any help.**
internal static let description = L10n.tr("Localizable", "UnstableFeaturesView.description", fallback: "Unstable Features are features that are currently being tested or still a work-in-progress and not ready for public usage. Because of this, they are only available on nightly builds. By default, all unstable features are off. Additionally, only more stable unstable features are available in Advanced Settings; most are locked behind Developer Mode to ensure normal users don't use them.\n\nEvery unstable feature has a tracking issue, which contains info on what the unstable feature adds and tracks the unstable feature status. To view a tracking issue for an unstable feature, simply click it in the list. **Please use the tracking issue for reporting bugs or giving feedback.**\n\n**Do not ask for support on using unstable features, you will not receive any help.**")
/// There are currently no unstable features available.
internal static let noUnstableFeatures = L10n.tr("Localizable", "UnstableFeaturesView.noUnstableFeatures", fallback: "There are currently no unstable features available.")
/// UnstableFeaturesView /// UnstableFeaturesView
internal static let title = L10n.tr("Localizable", "UnstableFeaturesView.title", fallback: "Unstable Features") internal static let title = L10n.tr("Localizable", "UnstableFeaturesView.title", fallback: "Unstable Features")
} }

View File

@@ -40,27 +40,38 @@ struct AdvancedSettingsView: View {
var body: some View { var body: some View {
List { List {
Section { Section {
Picker(L10n.AdvancedSettingsView.anisette, selection: $selectedAnisetteServer) { Picker(L10n.AdvancedSettingsView.AnisetteSettings.server, selection: $selectedAnisetteServer) {
ForEach(anisetteServers) { server in ForEach(anisetteServers) { server in
Text(server.display) Text(server.display)
} }
} }
}
Toggle(L10n.AdvancedSettingsView.AnisetteSettings.usePreferred, isOn: $usePreferred)
Section {
Toggle(L10n.AdvancedSettingsView.DangerZone.usePreferred, isOn: $usePreferred)
HStack { HStack {
Text(L10n.AdvancedSettingsView.DangerZone.anisetteURL) Text(L10n.AdvancedSettingsView.AnisetteSettings.anisetteURL)
TextField("", text: $anisetteURL) TextField("", text: $anisetteURL)
.autocapitalization(.none) .autocapitalization(.none)
.autocorrectionDisabled(true) .autocorrectionDisabled(true)
} }
} header: { } header: {
Text(L10n.AdvancedSettingsView.dangerZone) Text(L10n.AdvancedSettingsView.anisetteSettings)
} footer: { } footer: {
Text(L10n.AdvancedSettingsView.dangerZoneInfo) Text(L10n.AdvancedSettingsView.AnisetteSettings.footer)
} }
#if UNSTABLE // TODO: remove this once we have more settings for the danger zone.
Section {
#if UNSTABLE
NavigationLink(L10n.UnstableFeaturesView.title) {
UnstableFeaturesView(inDevMode: false)
}
.foregroundColor(.red)
#endif
} header: {
Text(L10n.AdvancedSettingsView.dangerZone)
}
#endif
} }
.navigationTitle(L10n.AdvancedSettingsView.title) .navigationTitle(L10n.AdvancedSettingsView.title)
.enableInjection() .enableInjection()

View File

@@ -128,6 +128,10 @@ struct DevModeMenu: View {
@AppStorage("isConsoleEnabled") @AppStorage("isConsoleEnabled")
var isConsoleEnabled: Bool = false var isConsoleEnabled: Bool = false
#if !UNSTABLE
@State var isUnstableAlertShowing = false
#endif
var body: some View { var body: some View {
List { List {
Section { Section {
@@ -143,6 +147,10 @@ struct DevModeMenu: View {
} }
#if !UNSTABLE #if !UNSTABLE
.disabled(true) .disabled(true)
.alert(isPresented: $isUnstableAlertShowing) {
Alert(title: Text(L10n.DevModeView.unstableFeaturesNightlyOnly))
}
.onTapGesture { isUnstableAlertShowing = true }
#endif #endif
NavigationLink(L10n.DevModeView.dataExplorer) { NavigationLink(L10n.DevModeView.dataExplorer) {

View File

@@ -104,6 +104,10 @@ struct SettingsView: View {
} }
Section { Section {
NavigationLink("Show Refresh Attempts") {
RefreshAttemptsView()
}
Toggle(isOn: self.$isBackgroundRefreshEnabled, label: { Toggle(isOn: self.$isBackgroundRefreshEnabled, label: {
Text(L10n.SettingsView.backgroundRefresh) Text(L10n.SettingsView.backgroundRefresh)
}) })
@@ -113,10 +117,6 @@ 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: {
@@ -171,12 +171,6 @@ struct SettingsView: View {
AdvancedSettingsView() AdvancedSettingsView()
} }
#if UNSTABLE
NavigationLink(L10n.UnstableFeaturesView.title) {
UnstableFeaturesView(inDevMode: 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

@@ -10,20 +10,36 @@
import SwiftUI import SwiftUI
struct UnstableFeaturesView: View { struct UnstableFeaturesView: View {
@ObservedObject private var shared = UnstableFeatures.shared @ObservedObject private var iO = Inject.observer
var inDevMode: Bool var inDevMode: Bool
var body: some View { var body: some View {
List { List {
ForEach(shared.features.filter { feature, _ in feature != .dummy && (inDevMode || feature.availableOutsideDevMode()) }.sorted(by: { _, _ in true }), id: \.key) { feature, _ in let features = UnstableFeatures.getFeatures(inDevMode)
Toggle(isOn: Binding(get: { UnstableFeatures.enabled(feature) }, set: { newValue in UnstableFeatures.set(feature, enabled: newValue) })) {
Text(String(describing: feature)) let description = L10n.UnstableFeaturesView.description + (features.count <= 0 ? "\n\n" + L10n.UnstableFeaturesView.noUnstableFeatures : "")
let link = "https://github.com/SideStore/SideStore/issues/\(feature.rawValue)" Section {} footer: {
Link(link, destination: URL(string: link)!) if #available(iOS 15.0, *),
let string = try? AttributedString(markdown: description, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)) {
Text(string).font(.callout).foregroundColor(.primary)
} else {
Text(description).font(.callout).foregroundColor(.primary)
}
}.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
if features.count > 0 {
ForEach(features.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) }
.navigationTitle(L10n.UnstableFeaturesView.title)
.enableInjection()
} }
} }

View File

@@ -22,7 +22,7 @@ enum AvailableUnstableFeature: String, CaseIterable {
func availableOutsideDevMode() -> Bool { func availableOutsideDevMode() -> Bool {
switch self { switch self {
// If your unstable feature is stable enough to be used by nightly users who are not alpha testers or developers, // If your unstable feature is stable enough to be used by nightly users who are not alpha testers or developers,
// you may want to have it available in the "Unstable Features" menu in Settings (outside of dev mode). To do so, add this: // you may want to have it available in the Unstable Features menu in Advanced Settings (outside of dev mode). To do so, add this:
//case .yourFeature: return true //case .yourFeature: return true
case .jitUrlScheme: return true case .jitUrlScheme: return true
@@ -34,31 +34,42 @@ enum AvailableUnstableFeature: String, CaseIterable {
class UnstableFeatures: ObservableObject { class UnstableFeatures: ObservableObject {
#if UNSTABLE #if UNSTABLE
static let shared = UnstableFeatures() private static var features: [AvailableUnstableFeature: Bool] = [:]
@Published var features: [AvailableUnstableFeature: Bool] = [:]
static func getFeatures(_ inDevMode: Bool) -> [(key: AvailableUnstableFeature, value: Bool)] {
return features
.filter { feature, _ in
feature != .dummy &&
(inDevMode || feature.availableOutsideDevMode())
}.sorted { a, b in a.key.rawValue > b.key.rawValue } // Convert to array of keys and values
}
static func load() { static func load() {
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 }) {
shared.features[feature] = rawFeature.value features[feature] = rawFeature.value
} else { } else {
print("Unknown unstable feature: \(rawFeature.key) = \(rawFeature.value)") print("Unknown unstable feature: \(rawFeature.key) = \(rawFeature.value)")
} }
} }
// If there's a new feature that wasn't saved and therefore wasn't loaded, let's set it to false
// Technically we shouldn't have to do this because enabled() will fallback to false
for feature in AvailableUnstableFeature.allCases { for feature in AvailableUnstableFeature.allCases {
if shared.features[feature] == nil { if features[feature] == nil {
shared.features[feature] = false 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 {
shared.features[feature] = false features[feature] = false
} }
save() save()
} }
@@ -66,7 +77,7 @@ class UnstableFeatures: ObservableObject {
private static func save(load: Bool = false) { private static func save(load: Bool = false) {
var rawFeatures: [String: Bool] = [:] var rawFeatures: [String: Bool] = [:]
for feature in shared.features { for feature in 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)
@@ -74,7 +85,7 @@ class UnstableFeatures: ObservableObject {
} }
static func set(_ feature: AvailableUnstableFeature, enabled: Bool) { static func set(_ feature: AvailableUnstableFeature, enabled: Bool) {
shared.features[feature] = enabled features[feature] = enabled
save() save()
} }
#endif #endif
@@ -82,7 +93,7 @@ class UnstableFeatures: ObservableObject {
@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
shared.features[feature] ?? false features[feature] ?? false
#else #else
false false
#endif #endif