mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Add developer mode
This commit is contained in:
@@ -83,7 +83,12 @@
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
|
||||
99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */; };
|
||||
99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */; };
|
||||
99D87A6529A04D5E00ED09A9 /* Inject in Frameworks */ = {isa = PBXBuildFile; productRef = 99D87A6429A04D5E00ED09A9 /* Inject */; };
|
||||
99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640029A1271100B920BF /* AsyncFallibleButton.swift */; };
|
||||
99DE640329A1624500B920BF /* View+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99DE640229A1624500B920BF /* View+SideStore.swift */; };
|
||||
99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 99DE640529A1753800B920BF /* ZIPFoundation */; };
|
||||
99E59E1D299BFE5D00FAF33D /* AppIconsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */; };
|
||||
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; };
|
||||
B3146ED3284F581E00BBC3FD /* Roxas.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B3146ECD284F580500BBC3FD /* Roxas.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@@ -633,6 +638,10 @@
|
||||
1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonProgressViewStyle.swift; sourceTree = "<group>"; };
|
||||
1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = "<group>"; };
|
||||
1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; };
|
||||
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevModeView.swift; sourceTree = "<group>"; };
|
||||
99D87A61299F3EC300ED09A9 /* FileExplorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileExplorer.swift; sourceTree = "<group>"; };
|
||||
99DE640029A1271100B920BF /* AsyncFallibleButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncFallibleButton.swift; sourceTree = "<group>"; };
|
||||
99DE640229A1624500B920BF /* View+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+SideStore.swift"; sourceTree = "<group>"; };
|
||||
99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsView.swift; sourceTree = "<group>"; };
|
||||
B3146EC6284F580500BBC3FD /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Roxas.xcodeproj; path = Dependencies/Roxas/Roxas.xcodeproj; sourceTree = "<group>"; };
|
||||
B33FFBA9295F8F78002259E6 /* preboard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = preboard.c; path = src/preboard.c; sourceTree = "<group>"; };
|
||||
@@ -1056,6 +1065,7 @@
|
||||
1F07F5672955D16A00F7BE95 /* SFSafeSymbols in Frameworks */,
|
||||
1FFA56C52999978C0011B6F5 /* LocalConsole in Frameworks */,
|
||||
BF66EE852501AE50007EE018 /* AltStoreCore.framework in Frameworks */,
|
||||
99DE640629A1753800B920BF /* ZIPFoundation in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1215,6 +1225,7 @@
|
||||
1F545E82298D79E400589F68 /* ErrorLogView.swift */,
|
||||
1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */,
|
||||
99E59E1C299BFE5D00FAF33D /* AppIconsView.swift */,
|
||||
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1241,6 +1252,8 @@
|
||||
1F6E08E529280F4B005059C0 /* RatingStars.swift */,
|
||||
1F0DD8422936B0F9007608A4 /* RoundedTextField.swift */,
|
||||
1F545E86298D86D800589F68 /* ModalNavigationLink.swift */,
|
||||
99D87A61299F3EC300ED09A9 /* FileExplorer.swift */,
|
||||
99DE640029A1271100B920BF /* AsyncFallibleButton.swift */,
|
||||
);
|
||||
path = "View Components";
|
||||
sourceTree = "<group>";
|
||||
@@ -1951,6 +1964,7 @@
|
||||
1F66F5BD2938F06100A910CA /* StoreApp+Filterable.swift */,
|
||||
1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */,
|
||||
1F180F93298E7A2500D1C98B /* Source+Trusted.swift */,
|
||||
99DE640229A1624500B920BF /* View+SideStore.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
@@ -2312,6 +2326,7 @@
|
||||
1F1295802989B51F0048FCB9 /* ExpandableText */,
|
||||
1FFA56C42999978C0011B6F5 /* LocalConsole */,
|
||||
99D87A6429A04D5E00ED09A9 /* Inject */,
|
||||
99DE640529A1753800B920BF /* ZIPFoundation */,
|
||||
);
|
||||
productName = AltStore;
|
||||
productReference = BFD2476A2284B9A500981D42 /* SideStore.app */;
|
||||
@@ -2389,6 +2404,7 @@
|
||||
1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */,
|
||||
1FFA56C32999978C0011B6F5 /* XCRemoteSwiftPackageReference "LocalConsole" */,
|
||||
99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */,
|
||||
99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||
);
|
||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -2758,12 +2774,14 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
BFDB6A0F22AB2776007EA6D6 /* SendAppOperation.swift in Sources */,
|
||||
99DE640129A1271100B920BF /* AsyncFallibleButton.swift in Sources */,
|
||||
1F6E08E029280B12005059C0 /* SafariView.swift in Sources */,
|
||||
1F943C6C2927F90400ABE095 /* SettingsView.swift in Sources */,
|
||||
BFDB6A0D22AAFC1A007EA6D6 /* OperationError.swift in Sources */,
|
||||
BF74989B23621C0700CED65F /* ForwardingNavigationController.swift in Sources */,
|
||||
1F66F5C02938F07C00A910CA /* Filterable.swift in Sources */,
|
||||
BF3D649D22E7AC1B00E9056B /* PermissionPopoverViewController.swift in Sources */,
|
||||
99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */,
|
||||
D57F2C9426E01BC700B9FA39 /* UIDevice+Vibration.swift in Sources */,
|
||||
1F07F556295458D800F7BE95 /* Assets.swift in Sources */,
|
||||
BFD2478F2284C8F900981D42 /* Button.swift in Sources */,
|
||||
@@ -2835,6 +2853,7 @@
|
||||
BFE338E822F10E56002E24B9 /* LaunchViewController.swift in Sources */,
|
||||
BFA8172B23C5633D001B5953 /* FetchAnisetteDataOperation.swift in Sources */,
|
||||
BF9ABA4722DD0638008935CF /* BrowseCollectionViewCell.swift in Sources */,
|
||||
99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */,
|
||||
BFD6B03322DFF20800B86064 /* MyAppsComponents.swift in Sources */,
|
||||
BF41B808233433C100C593A3 /* LoadingState.swift in Sources */,
|
||||
BFF0B69A2322D7D0007A79E1 /* UIScreen+CompactHeight.swift in Sources */,
|
||||
@@ -2871,6 +2890,7 @@
|
||||
BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */,
|
||||
BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */,
|
||||
1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */,
|
||||
99DE640329A1624500B920BF /* View+SideStore.swift in Sources */,
|
||||
1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */,
|
||||
1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */,
|
||||
1F66F5BA2938CA5700A910CA /* VisualEffectView.swift in Sources */,
|
||||
@@ -3809,6 +3829,14 @@
|
||||
minimumVersion = 1.0.0;
|
||||
};
|
||||
};
|
||||
99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/weichsel/ZIPFoundation.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.9.9;
|
||||
};
|
||||
};
|
||||
B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/kishikawakatsumi/KeychainAccess.git";
|
||||
@@ -3920,6 +3948,11 @@
|
||||
package = 99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */;
|
||||
productName = Inject;
|
||||
};
|
||||
99DE640529A1753800B920BF /* ZIPFoundation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
|
||||
productName = ZIPFoundation;
|
||||
};
|
||||
B3C395F0284F2DE700DA9E2F /* KeychainAccess */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = B3C395EF284F2DE700DA9E2F /* XCRemoteSwiftPackageReference "KeychainAccess" */;
|
||||
|
||||
@@ -134,6 +134,15 @@
|
||||
"branch" : "master",
|
||||
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "zipfoundation",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/weichsel/ZIPFoundation.git",
|
||||
"state" : {
|
||||
"revision" : "43ec568034b3731101dbf7670765d671c30f54f3",
|
||||
"version" : "0.9.16"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
||||
@@ -10,6 +10,7 @@ import UIKit
|
||||
import UserNotifications
|
||||
import AVFoundation
|
||||
import Intents
|
||||
import LocalConsole
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
@@ -64,6 +65,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
// Register default settings before doing anything else.
|
||||
UserDefaults.registerDefaults()
|
||||
|
||||
LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled
|
||||
|
||||
DatabaseManager.shared.start { (error) in
|
||||
if let error = error
|
||||
{
|
||||
|
||||
22
AltStore/Extensions/View+SideStore.swift
Normal file
22
AltStore/Extensions/View+SideStore.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// View+SideStore.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by naturecodevoid on 2/18/23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
// https://stackoverflow.com/a/59228385 (modified)
|
||||
extension View {
|
||||
@ViewBuilder func isHidden(_ hidden: Binding<Bool>, remove: Bool = false) -> some View {
|
||||
if hidden.wrappedValue {
|
||||
if !remove {
|
||||
self.hidden()
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,14 @@ internal enum Asset {
|
||||
internal static let blurTint = ColorAsset(name: "BlurTint")
|
||||
internal static let settingsBackground = ColorAsset(name: "SettingsBackground")
|
||||
internal static let settingsHighlighted = ColorAsset(name: "SettingsHighlighted")
|
||||
internal static let honeydewImage = ImageAsset(name: "Honeydew-image")
|
||||
internal static let midnightImage = ImageAsset(name: "Midnight-image")
|
||||
internal static let neonImage = ImageAsset(name: "Neon-image")
|
||||
internal static let next = ImageAsset(name: "Next")
|
||||
internal static let skyImage = ImageAsset(name: "Sky-image")
|
||||
internal static let starburstImage = ImageAsset(name: "Starburst-image")
|
||||
internal static let steelImage = ImageAsset(name: "Steel-image")
|
||||
internal static let stormImage = ImageAsset(name: "Storm-image")
|
||||
internal static let browse = ImageAsset(name: "Browse")
|
||||
internal static let myApps = ImageAsset(name: "MyApps")
|
||||
internal static let news = ImageAsset(name: "News")
|
||||
|
||||
@@ -15,6 +15,8 @@ internal enum L10n {
|
||||
internal static let close = L10n.tr("Localizable", "Action.close", fallback: "Close")
|
||||
/// General Actions
|
||||
internal static let done = L10n.tr("Localizable", "Action.done", fallback: "Done")
|
||||
/// Enable
|
||||
internal static let enable = L10n.tr("Localizable", "Action.enable", fallback: "Enable")
|
||||
}
|
||||
internal enum AddSourceView {
|
||||
/// Continue
|
||||
@@ -150,6 +152,10 @@ internal enum L10n {
|
||||
/// AppRowView
|
||||
internal static let sideloaded = L10n.tr("Localizable", "AppRowView.sideloaded", fallback: "Sideloaded")
|
||||
}
|
||||
internal enum AsyncFallibleButton {
|
||||
/// AsyncFallibleButton
|
||||
internal static let error = L10n.tr("Localizable", "AsyncFallibleButton.error", fallback: "An error occurred")
|
||||
}
|
||||
internal enum BrowseView {
|
||||
/// Search
|
||||
internal static let search = L10n.tr("Localizable", "BrowseView.search", fallback: "Search")
|
||||
@@ -223,6 +229,41 @@ internal enum L10n {
|
||||
/// Why do we need this?
|
||||
internal static let whyDoWeNeedThis = L10n.tr("Localizable", "ConnectAppleIDView.whyDoWeNeedThis", fallback: "Why do we need this?")
|
||||
}
|
||||
internal enum DevModeView {
|
||||
/// Console
|
||||
internal static let console = L10n.tr("Localizable", "DevModeView.console", fallback: "Console")
|
||||
/// Data File Explorer
|
||||
internal static let dataExplorer = L10n.tr("Localizable", "DevModeView.dataExplorer", fallback: "Data File Explorer")
|
||||
/// minimuxer debug actions
|
||||
internal static let minimuxer = L10n.tr("Localizable", "DevModeView.minimuxer", fallback: "minimuxer debug actions")
|
||||
/// SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**
|
||||
///
|
||||
/// You should only enable Developer Mode if you meet one of the following requirements:
|
||||
/// - You are a SideStore developer or contributor
|
||||
/// - When getting support, you were asked to do this by a helper
|
||||
/// - You were asked to do this when you reported a bug or helped a developer test a change
|
||||
///
|
||||
/// **_We will not provide support if you break SideStore with Developer Mode._**
|
||||
internal static let prompt = L10n.tr("Localizable", "DevModeView.prompt", fallback: "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**\n\nYou should only enable Developer Mode if you meet one of the following requirements:\n- You are a SideStore developer or contributor\n- When getting support, you were asked to do this by a helper\n- You were asked to do this when you reported a bug or helped a developer test a change\n\n**_We will not provide support if you break SideStore with Developer Mode._**")
|
||||
/// Read the text!
|
||||
internal static let read = L10n.tr("Localizable", "DevModeView.read", fallback: "Read the text!")
|
||||
/// Skip Resign
|
||||
internal static let skipResign = L10n.tr("Localizable", "DevModeView.skipResign", fallback: "Skip Resign")
|
||||
/// Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError
|
||||
internal static let skipResignInfo = L10n.tr("Localizable", "DevModeView.skipResignInfo", fallback: "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError")
|
||||
/// DevModeView
|
||||
internal static let title = L10n.tr("Localizable", "DevModeView.title", fallback: "Developer Mode")
|
||||
/// Temporary File Explorer
|
||||
internal static let tmpExplorer = L10n.tr("Localizable", "DevModeView.tmpExplorer", fallback: "Temporary File Explorer")
|
||||
internal enum Minimuxer {
|
||||
/// Dump provisioning profiles to Documents directory
|
||||
internal static let dumpProfiles = L10n.tr("Localizable", "DevModeView.Minimuxer.dumpProfiles", fallback: "Dump provisioning profiles to Documents directory")
|
||||
/// PublicStaging File Explorer
|
||||
internal static let stagingExplorer = L10n.tr("Localizable", "DevModeView.Minimuxer.stagingExplorer", fallback: "PublicStaging File Explorer")
|
||||
/// View provisioning profiles
|
||||
internal static let viewProfiles = L10n.tr("Localizable", "DevModeView.Minimuxer.viewProfiles", fallback: "View provisioning profiles")
|
||||
}
|
||||
}
|
||||
internal enum MyAppsView {
|
||||
/// MyAppsView
|
||||
internal static let active = L10n.tr("Localizable", "MyAppsView.active", fallback: "Active")
|
||||
|
||||
@@ -204,6 +204,8 @@
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
</dict>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
import Roxas
|
||||
import SwiftUI
|
||||
import ZIPFoundation
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
@@ -15,6 +17,9 @@ import AltSign
|
||||
@objc(ResignAppOperation)
|
||||
final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
{
|
||||
static var skipResign: Bool = false
|
||||
static var skipResignBinding: Binding<Bool> { Binding<Bool>(get: { skipResign }, set: { skipResign = $0 }) }
|
||||
|
||||
let context: InstallAppOperationContext
|
||||
|
||||
init(context: InstallAppOperationContext)
|
||||
@@ -50,6 +55,23 @@ final class ResignAppOperation: ResultOperation<ALTApplication>
|
||||
let prepareAppBundleProgress = self.prepareAppBundle(for: app, profiles: profiles) { (result) in
|
||||
guard let appBundleURL = self.process(result) else { return }
|
||||
|
||||
if ResignAppOperation.skipResign {
|
||||
print("⚠️ WARNING: Skipping resign. Unless you correctly resigned the IPA before installing it, things will not work! Also, this might crash SideStore. You have been warned!")
|
||||
let ipaFile = self.context.temporaryDirectory.appendingPathComponent("App.ipa")
|
||||
let archive = Archive(url: ipaFile, accessMode: .create)!
|
||||
for case let fileURL as URL in FileManager.default.enumerator(at: appBundleURL, includingPropertiesForKeys: [])! {
|
||||
let relative = fileURL.description.replacingOccurrences(of: appBundleURL.description, with: "").removingPercentEncoding!
|
||||
try! archive.addEntry(with: "Payload/App.app\(relative)", fileURL: fileURL)
|
||||
}
|
||||
let destinationURL = InstalledApp.refreshedIPAURL(for: app)
|
||||
try! FileManager.default.copyItem(at: ipaFile, to: destinationURL, shouldReplace: true)
|
||||
|
||||
// Use appBundleURL since we need an app bundle, not .ipa.
|
||||
let resignedApplication = ALTApplication(fileURL: appBundleURL)!
|
||||
self.finish(.success(resignedApplication))
|
||||
return
|
||||
}
|
||||
|
||||
print("Resigning App:", self.context.bundleIdentifier)
|
||||
|
||||
// Resign app bundle
|
||||
|
||||
@@ -10,13 +10,12 @@
|
||||
/* General Actions */
|
||||
"Action.done" = "Done";
|
||||
"Action.close" = "Close";
|
||||
|
||||
"Action.enable" = "Enable";
|
||||
|
||||
/* NewsView */
|
||||
"NewsView.title" = "News";
|
||||
"NewsView.Section.FromSources.title" = "From your Sources";
|
||||
|
||||
|
||||
/* BrowseView */
|
||||
"BrowseView.title" = "Browse";
|
||||
"BrowseView.search" = "Search";
|
||||
@@ -166,3 +165,27 @@
|
||||
|
||||
/* AppIconsView */
|
||||
"AppIconsView.title" = "App Icon";
|
||||
|
||||
/* DevModeView */
|
||||
"DevModeView.title" = "Developer Mode";
|
||||
"DevModeView.prompt" = "SideStore's Developer Mode gives access to a menu with some debugging actions commonly used by developers. **However, some of them can break SideStore if used in the wrong way.**
|
||||
|
||||
You should only enable Developer Mode if you meet one of the following requirements:
|
||||
- You are a SideStore developer or contributor
|
||||
- When getting support, you were asked to do this by a helper
|
||||
- You were asked to do this when you reported a bug or helped a developer test a change
|
||||
|
||||
**_We will not provide support if you break SideStore with Developer Mode._**";
|
||||
"DevModeView.read" = "Read the text!";
|
||||
"DevModeView.console" = "Console";
|
||||
"DevModeView.dataExplorer" = "Data File Explorer";
|
||||
"DevModeView.tmpExplorer" = "Temporary File Explorer";
|
||||
"DevModeView.skipResign" = "Skip Resign";
|
||||
"DevModeView.skipResignInfo" = "Skip Resign should only be used when you have an IPA that you have self signed. Otherwise, it will break things, and might make SideStore crash (there is absolutely no error handling and everything is expected to work). Useful for debugging ApplicationVerificationError";
|
||||
"DevModeView.minimuxer" = "minimuxer debug actions";
|
||||
"DevModeView.Minimuxer.stagingExplorer" = "PublicStaging File Explorer";
|
||||
"DevModeView.Minimuxer.viewProfiles" = "View provisioning profiles";
|
||||
"DevModeView.Minimuxer.dumpProfiles" = "Dump provisioning profiles to Documents directory";
|
||||
|
||||
/* AsyncFallibleButton */
|
||||
"AsyncFallibleButton.error" = "An error occurred";
|
||||
|
||||
118
AltStore/View Components/AsyncFallibleButton.swift
Normal file
118
AltStore/View Components/AsyncFallibleButton.swift
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// AsyncFallibleButton.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by naturecodevoid on 2/18/23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
private enum AsyncFallibleButtonState {
|
||||
case none
|
||||
case loading
|
||||
case success
|
||||
case error
|
||||
}
|
||||
|
||||
struct AsyncFallibleButton<Label: View>: View {
|
||||
@ObservedObject private var iO = Inject.observer
|
||||
|
||||
let action: () throws -> Void
|
||||
let label: (_ execute: @escaping () -> Void) -> Label
|
||||
|
||||
var afterFinish: (_ success: Bool) -> Void = { success in } // runs after the checkmark/X has disappeared
|
||||
var wrapInButton = true
|
||||
var secondsToDisplayResultIcon: Double = 3
|
||||
|
||||
@State private var state: AsyncFallibleButtonState = .none
|
||||
@State private var showErrorAlert = false
|
||||
@State private var errorAlertMessage = ""
|
||||
|
||||
private var inside: some View {
|
||||
HStack {
|
||||
label(execute)
|
||||
if state != .none {
|
||||
if wrapInButton {
|
||||
Spacer()
|
||||
}
|
||||
switch (state) {
|
||||
case .loading:
|
||||
ProgressView()
|
||||
case .success:
|
||||
Image(systemSymbol: .checkmark)
|
||||
.foregroundColor(Color.green)
|
||||
case .error:
|
||||
Image(systemSymbol: .xmark)
|
||||
.foregroundColor(Color.red)
|
||||
default:
|
||||
Image(systemSymbol: .questionmark)
|
||||
.foregroundColor(Color.yellow)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var wrapped: some View {
|
||||
if wrapInButton {
|
||||
return AnyView(SwiftUI.Button(action: {
|
||||
execute()
|
||||
}) {
|
||||
inside
|
||||
})
|
||||
} else {
|
||||
return AnyView(inside)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
wrapped
|
||||
.alert(isPresented: $showErrorAlert) {
|
||||
Alert(
|
||||
title: Text(L10n.AsyncFallibleButton.error),
|
||||
message: Text(errorAlertMessage)
|
||||
)
|
||||
}
|
||||
.disabled(state != .none)
|
||||
.animation(.default, value: state)
|
||||
.enableInjection()
|
||||
}
|
||||
|
||||
func execute() {
|
||||
if state != .none { return }
|
||||
state = .loading
|
||||
DispatchQueue.global().async {
|
||||
do {
|
||||
try action()
|
||||
DispatchQueue.main.async { state = .success }
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
state = .error
|
||||
errorAlertMessage = error.localizedDescription
|
||||
showErrorAlert = true
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + secondsToDisplayResultIcon) {
|
||||
let lastState = state
|
||||
state = .none
|
||||
afterFinish(lastState == .success)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AsyncFallibleButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AsyncFallibleButton(action: {
|
||||
print("Start")
|
||||
for index in 0...5000000 {
|
||||
_ = index + index
|
||||
}
|
||||
throw NSError(domain: "TestError", code: -1)
|
||||
//print("Finish")
|
||||
}) { execute in
|
||||
Text("Hello World")
|
||||
}
|
||||
}
|
||||
}
|
||||
366
AltStore/View Components/FileExplorer.swift
Normal file
366
AltStore/View Components/FileExplorer.swift
Normal file
@@ -0,0 +1,366 @@
|
||||
//
|
||||
// FileExplorer.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by naturecodevoid on 2/16/23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import ZIPFoundation
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
// https://stackoverflow.com/a/72165424
|
||||
func allUTITypes() -> [UTType] {
|
||||
let types: [UTType] =
|
||||
[.item,
|
||||
.content,
|
||||
.compositeContent,
|
||||
.diskImage,
|
||||
.data,
|
||||
.directory,
|
||||
.resolvable,
|
||||
.symbolicLink,
|
||||
.executable,
|
||||
.mountPoint,
|
||||
.aliasFile,
|
||||
.urlBookmarkData,
|
||||
.url,
|
||||
.fileURL,
|
||||
.text,
|
||||
.plainText,
|
||||
.utf8PlainText,
|
||||
.utf16ExternalPlainText,
|
||||
.utf16PlainText,
|
||||
.delimitedText,
|
||||
.commaSeparatedText,
|
||||
.tabSeparatedText,
|
||||
.utf8TabSeparatedText,
|
||||
.rtf,
|
||||
.html,
|
||||
.xml,
|
||||
.yaml,
|
||||
.sourceCode,
|
||||
.assemblyLanguageSource,
|
||||
.cSource,
|
||||
.objectiveCSource,
|
||||
.swiftSource,
|
||||
.cPlusPlusSource,
|
||||
.objectiveCPlusPlusSource,
|
||||
.cHeader,
|
||||
.cPlusPlusHeader]
|
||||
|
||||
let types_1: [UTType] =
|
||||
[.script,
|
||||
.appleScript,
|
||||
.osaScript,
|
||||
.osaScriptBundle,
|
||||
.javaScript,
|
||||
.shellScript,
|
||||
.perlScript,
|
||||
.pythonScript,
|
||||
.rubyScript,
|
||||
.phpScript,
|
||||
.json,
|
||||
.propertyList,
|
||||
.xmlPropertyList,
|
||||
.binaryPropertyList,
|
||||
.pdf,
|
||||
.rtfd,
|
||||
.flatRTFD,
|
||||
.webArchive,
|
||||
.image,
|
||||
.jpeg,
|
||||
.tiff,
|
||||
.gif,
|
||||
.png,
|
||||
.icns,
|
||||
.bmp,
|
||||
.ico,
|
||||
.rawImage,
|
||||
.svg,
|
||||
.livePhoto,
|
||||
.heif,
|
||||
.heic,
|
||||
.webP,
|
||||
.threeDContent,
|
||||
.usd,
|
||||
.usdz,
|
||||
.realityFile,
|
||||
.sceneKitScene,
|
||||
.arReferenceObject,
|
||||
.audiovisualContent]
|
||||
|
||||
let types_2: [UTType] =
|
||||
[.movie,
|
||||
.video,
|
||||
.audio,
|
||||
.quickTimeMovie,
|
||||
UTType("com.apple.quicktime-image"),
|
||||
.mpeg,
|
||||
.mpeg2Video,
|
||||
.mpeg2TransportStream,
|
||||
.mp3,
|
||||
.mpeg4Movie,
|
||||
.mpeg4Audio,
|
||||
.appleProtectedMPEG4Audio,
|
||||
.appleProtectedMPEG4Video,
|
||||
.avi,
|
||||
.aiff,
|
||||
.wav,
|
||||
.midi,
|
||||
.playlist,
|
||||
.m3uPlaylist,
|
||||
.folder,
|
||||
.volume,
|
||||
.package,
|
||||
.bundle,
|
||||
.pluginBundle,
|
||||
.spotlightImporter,
|
||||
.quickLookGenerator,
|
||||
.xpcService,
|
||||
.framework,
|
||||
.application,
|
||||
.applicationBundle,
|
||||
.applicationExtension,
|
||||
.unixExecutable,
|
||||
.exe,
|
||||
.systemPreferencesPane,
|
||||
.archive,
|
||||
.gzip,
|
||||
.bz2,
|
||||
.zip,
|
||||
.appleArchive,
|
||||
.spreadsheet,
|
||||
.presentation,
|
||||
.database,
|
||||
.message,
|
||||
.contact,
|
||||
.vCard,
|
||||
.toDoItem,
|
||||
.calendarEvent,
|
||||
.emailMessage,
|
||||
.internetLocation,
|
||||
.internetShortcut,
|
||||
.font,
|
||||
.bookmark,
|
||||
.pkcs12,
|
||||
.x509Certificate,
|
||||
.epub,
|
||||
.log]
|
||||
.compactMap({ $0 })
|
||||
|
||||
return types + types_1 + types_2
|
||||
}
|
||||
|
||||
extension Binding<URL?>: Equatable {
|
||||
public static func == (lhs: Binding<URL?>, rhs: Binding<URL?>) -> Bool {
|
||||
return lhs.wrappedValue == rhs.wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
private struct DirectoryEntry: Identifiable {
|
||||
var id = UUID()
|
||||
var path: URL
|
||||
var parent: URL
|
||||
var isFile = false
|
||||
var childFiles = [URL]()
|
||||
var childDirectories: [DirectoryEntry]?
|
||||
var filesAndDirectories: [DirectoryEntry]? {
|
||||
if childFiles.count <= 0 { return childDirectories }
|
||||
|
||||
var filesAndDirectories = childDirectories ?? []
|
||||
for file in childFiles {
|
||||
filesAndDirectories.insert(DirectoryEntry(path: file, parent: path, isFile: true), at: 0)
|
||||
}
|
||||
|
||||
return filesAndDirectories.sorted(by: { $0.asString < $1.asString })
|
||||
}
|
||||
var asString: String {
|
||||
let str = path.description.replacingOccurrences(of: parent.description, with: "").removingPercentEncoding!
|
||||
if str.count <= 0 {
|
||||
return "/"
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
private enum FileExplorerAction {
|
||||
case delete
|
||||
case zip
|
||||
case insert
|
||||
}
|
||||
|
||||
private struct File: View {
|
||||
@ObservedObject private var iO = Inject.observer
|
||||
|
||||
var item: DirectoryEntry
|
||||
@Binding var explorerHidden: Bool
|
||||
|
||||
@State var quickLookURL: URL?
|
||||
@State var fileExplorerAction: FileExplorerAction?
|
||||
@State var hidden = false
|
||||
@State var isShowingFilePicker = false
|
||||
@State var selectedFile: URL?
|
||||
|
||||
var body: some View {
|
||||
AsyncFallibleButton(action: {
|
||||
switch (fileExplorerAction) {
|
||||
case .delete:
|
||||
print("deleting \(item.path.description)")
|
||||
try FileManager.default.removeItem(at: item.path)
|
||||
|
||||
case .zip:
|
||||
print("zipping \(item.path.description)")
|
||||
let dest = FileManager.default.documentsDirectory.appendingPathComponent(item.path.pathComponents.last! + ".zip")
|
||||
do {
|
||||
try FileManager.default.removeItem(at: dest)
|
||||
} catch {}
|
||||
|
||||
try FileManager.default.zipItem(at: item.path, to: dest)
|
||||
|
||||
case .insert:
|
||||
print("inserting \(selectedFile!.description) to \(item.path.description)")
|
||||
|
||||
try FileManager.default.copyItem(at: selectedFile!, to: item.path.appendingPathComponent(selectedFile!.pathComponents.last!))
|
||||
explorerHidden = true
|
||||
explorerHidden = false
|
||||
|
||||
default:
|
||||
print("unknown action for \(item.path.description): \(String(describing: fileExplorerAction))")
|
||||
}
|
||||
}, label: { execute in
|
||||
HStack {
|
||||
Text(item.asString)
|
||||
if item.isFile {
|
||||
Text(getFileSize(file: item.path)).foregroundColor(.secondary)
|
||||
}
|
||||
Spacer()
|
||||
Menu {
|
||||
if item.isFile {
|
||||
SwiftUI.Button(action: { quickLookURL = item.path }) {
|
||||
Label("View/Share", systemSymbol: .eye)
|
||||
}
|
||||
} else {
|
||||
SwiftUI.Button(action: {
|
||||
fileExplorerAction = .zip
|
||||
execute()
|
||||
}) {
|
||||
Label("Save to ZIP file", systemSymbol: .squareAndArrowDown)
|
||||
}
|
||||
|
||||
SwiftUI.Button {
|
||||
isShowingFilePicker = true
|
||||
} label: {
|
||||
Label("Insert file", systemSymbol: .plus)
|
||||
}
|
||||
}
|
||||
|
||||
if item.asString != "/" {
|
||||
SwiftUI.Button(action: {
|
||||
fileExplorerAction = .delete
|
||||
execute()
|
||||
}) {
|
||||
Label("Delete", systemSymbol: .trash)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemSymbol: .ellipsis)
|
||||
.frame(width: 20, height: 20) // Make it easier to tap
|
||||
}
|
||||
}
|
||||
.onChange(of: $selectedFile) { file in
|
||||
guard file.wrappedValue != nil else { return }
|
||||
|
||||
fileExplorerAction = .insert
|
||||
execute()
|
||||
}
|
||||
}, afterFinish: { success in
|
||||
switch (fileExplorerAction) {
|
||||
case .delete:
|
||||
if success { hidden = true }
|
||||
|
||||
case .zip:
|
||||
UIApplication.shared.open(URL(string: "shareddocuments://" + FileManager.default.documentsDirectory.description.replacingOccurrences(of: "file://", with: ""))!, options: [:], completionHandler: nil)
|
||||
|
||||
default: break
|
||||
}
|
||||
}, wrapInButton: false)
|
||||
.quickLookPreview($quickLookURL)
|
||||
.sheet(isPresented: $isShowingFilePicker) {
|
||||
DocumentPicker(selectedUrl: $selectedFile, supportedTypes: allUTITypes().map({ $0.identifier }))
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
.isHidden($hidden)
|
||||
.enableInjection()
|
||||
}
|
||||
|
||||
func getFileSize(file: URL) -> String {
|
||||
guard let attributes = try? FileManager.default.attributesOfItem(atPath: file.description.replacingOccurrences(of: "file://", with: "")) else { return "Unknown file size" }
|
||||
var bytes = attributes[FileAttributeKey.size] as! Double
|
||||
|
||||
// https://stackoverflow.com/a/14919494 (ported to swift)
|
||||
let thresh = 1024.0;
|
||||
|
||||
if (bytes < thresh) {
|
||||
return String(describing: bytes) + " B";
|
||||
}
|
||||
|
||||
let units = ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var u = -1;
|
||||
|
||||
while (bytes >= thresh && u < units.count - 1) {
|
||||
bytes /= thresh;
|
||||
u += 1;
|
||||
}
|
||||
|
||||
return String(format: "%.2f", bytes) + " " + units[u];
|
||||
}
|
||||
}
|
||||
|
||||
struct FileExplorer: View {
|
||||
@ObservedObject private var iO = Inject.observer
|
||||
|
||||
var url: URL?
|
||||
|
||||
@State var hidden = false
|
||||
|
||||
var body: some View {
|
||||
List([iterateOverDirectory(directory: url!, parent: url!)], children: \.filesAndDirectories) { item in
|
||||
File(item: item, explorerHidden: $hidden)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
SwiftUI.Button {
|
||||
hidden = true
|
||||
hidden = false
|
||||
} label: {
|
||||
Image(systemSymbol: .arrowClockwise)
|
||||
}
|
||||
}
|
||||
}
|
||||
.isHidden($hidden)
|
||||
.enableInjection()
|
||||
}
|
||||
|
||||
private func iterateOverDirectory(directory: URL, parent: URL) -> DirectoryEntry {
|
||||
var directoryEntry = DirectoryEntry(path: directory, parent: parent)
|
||||
if let contents = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: []) {
|
||||
for entry in contents {
|
||||
if entry.hasDirectoryPath {
|
||||
if directoryEntry.childDirectories == nil { directoryEntry.childDirectories = [] }
|
||||
directoryEntry.childDirectories!.append(iterateOverDirectory(directory: entry, parent: directory))
|
||||
} else {
|
||||
directoryEntry.childFiles.append(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
return directoryEntry
|
||||
}
|
||||
}
|
||||
|
||||
struct FileExplorer_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@
|
||||
import SwiftUI
|
||||
import SFSafeSymbols
|
||||
|
||||
struct Icon: Identifiable {
|
||||
private struct Icon: Identifiable {
|
||||
var id: String { assetName }
|
||||
var displayName: String
|
||||
let assetName: String
|
||||
}
|
||||
|
||||
struct SpecialIcon {
|
||||
private struct SpecialIcon {
|
||||
let assetName: String
|
||||
let suffix: String?
|
||||
let forceIndex: Int?
|
||||
|
||||
147
AltStore/Views/Settings/DevModeView.swift
Normal file
147
AltStore/Views/Settings/DevModeView.swift
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// DevModeView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by naturecodevoid on 2/16/23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import LocalConsole
|
||||
|
||||
struct DevModePrompt: View {
|
||||
@Binding var isShowingDevModePrompt: Bool
|
||||
@Binding var isShowingDevModeMenu: Bool
|
||||
|
||||
@State var countdown = 0
|
||||
|
||||
var button: some View {
|
||||
SwiftUI.Button(action: {
|
||||
UserDefaults.standard.isDevModeEnabled = true
|
||||
isShowingDevModePrompt = false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
|
||||
isShowingDevModeMenu = true
|
||||
}
|
||||
}) {
|
||||
Text(countdown <= 0 ? L10n.Action.enable + " " + L10n.DevModeView.title : L10n.DevModeView.read + " (\(countdown))")
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
.disabled(countdown > 0)
|
||||
}
|
||||
|
||||
var text: some View {
|
||||
if #available(iOS 15.0, *) {
|
||||
do {
|
||||
return Text(try AttributedString(markdown: L10n.DevModeView.prompt, options: AttributedString.MarkdownParsingOptions(interpretedSyntax: .inlineOnlyPreservingWhitespace)))
|
||||
} catch {
|
||||
return Text(L10n.DevModeView.prompt)
|
||||
}
|
||||
} else {
|
||||
return Text(L10n.DevModeView.prompt)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
VStack {
|
||||
text
|
||||
.foregroundColor(.primary)
|
||||
.padding(.bottom)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
button.buttonStyle(.bordered)
|
||||
} else {
|
||||
button
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.navigationTitle(L10n.DevModeView.title)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
SwiftUI.Button(action: { isShowingDevModePrompt = false }) {
|
||||
Text(L10n.Action.close)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
countdown = 20
|
||||
tickCountdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func tickCountdown() {
|
||||
if countdown <= 0 { return }
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
countdown -= 1
|
||||
tickCountdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DevModeMenu: View {
|
||||
@ObservedObject private var iO = Inject.observer
|
||||
|
||||
@AppStorage("isConsoleEnabled")
|
||||
var isConsoleEnabled: Bool = false
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Toggle(L10n.DevModeView.console, isOn: self.$isConsoleEnabled)
|
||||
.onChange(of: self.isConsoleEnabled) { value in
|
||||
LCManager.shared.isVisible = value
|
||||
}
|
||||
|
||||
NavigationLink(L10n.DevModeView.dataExplorer) {
|
||||
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
|
||||
.navigationTitle(L10n.DevModeView.dataExplorer)
|
||||
}.foregroundColor(.red)
|
||||
|
||||
NavigationLink(L10n.DevModeView.tmpExplorer) {
|
||||
FileExplorer(url: FileManager.default.temporaryDirectory)
|
||||
.navigationTitle(L10n.DevModeView.tmpExplorer)
|
||||
}.foregroundColor(.red)
|
||||
|
||||
Toggle(L10n.DevModeView.skipResign, isOn: ResignAppOperation.skipResignBinding)
|
||||
.foregroundColor(.red)
|
||||
} footer: {
|
||||
Text(L10n.DevModeView.skipResignInfo)
|
||||
}
|
||||
|
||||
Section {
|
||||
NavigationLink(L10n.DevModeView.Minimuxer.stagingExplorer + " (Coming soon, needs minimuxer additions)") {
|
||||
FileExplorer(url: FileManager.default.altstoreSharedDirectory)
|
||||
.navigationTitle(L10n.DevModeView.Minimuxer.stagingExplorer)
|
||||
}.foregroundColor(.red).disabled(true)
|
||||
|
||||
NavigationLink(L10n.DevModeView.Minimuxer.viewProfiles + " (Coming soon, needs minimuxer additions)") {
|
||||
|
||||
}.disabled(true)
|
||||
|
||||
SwiftUI.Button(L10n.DevModeView.Minimuxer.dumpProfiles + " (Coming soon, needs minimuxer additions)", action: {
|
||||
// TODO: dump profiles to Documents/ProfileDump/[current time]
|
||||
}).disabled(true)
|
||||
} header: {
|
||||
Text(L10n.DevModeView.minimuxer)
|
||||
}
|
||||
}
|
||||
.navigationTitle(L10n.DevModeView.title)
|
||||
.enableInjection()
|
||||
}
|
||||
}
|
||||
|
||||
struct DevModeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
List {
|
||||
NavigationLink("DevModeMenu") {
|
||||
DevModeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,15 @@ struct SettingsView: View {
|
||||
@AppStorage("isBackgroundRefreshEnabled")
|
||||
var isBackgroundRefreshEnabled: Bool = true
|
||||
|
||||
@AppStorage("isDevModeEnabled")
|
||||
var isDevModeEnabled: Bool = false
|
||||
|
||||
@State var isShowingConnectAppleIDView = false
|
||||
@State var isShowingAddShortcutView = false
|
||||
@State var isShowingFeedbackMailView = false
|
||||
@State var isShowingResetPairingFileConfirmation = false
|
||||
@State var isShowingDevModePrompt = false
|
||||
@State var isShowingDevModeMenu = false
|
||||
|
||||
@State var externalURLToShow: URL?
|
||||
|
||||
@@ -163,10 +168,6 @@ struct SettingsView: View {
|
||||
RefreshAttemptsView()
|
||||
}
|
||||
|
||||
SwiftUI.Button("Toggle Console") {
|
||||
LCManager.shared.isVisible.toggle()
|
||||
}
|
||||
|
||||
if MailComposeView.canSendMail {
|
||||
SwiftUI.Button("Send Feedback") {
|
||||
self.isShowingFeedbackMailView = true
|
||||
@@ -199,6 +200,21 @@ struct SettingsView: View {
|
||||
.cancel()
|
||||
])
|
||||
}
|
||||
|
||||
if isDevModeEnabled {
|
||||
NavigationLink(L10n.DevModeView.title, isActive: self.$isShowingDevModeMenu) {
|
||||
DevModeMenu()
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
} else {
|
||||
SwiftUI.Button(L10n.DevModeView.title) {
|
||||
self.isShowingDevModePrompt = true
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
.sheet(isPresented: self.$isShowingDevModePrompt) {
|
||||
DevModePrompt(isShowingDevModePrompt: self.$isShowingDevModePrompt, isShowingDevModeMenu: self.$isShowingDevModeMenu)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.SettingsView.debug)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ public extension UserDefaults
|
||||
@NSManaged var customAnisetteURL: String?
|
||||
@NSManaged var preferredServerID: String?
|
||||
|
||||
@NSManaged var isDevModeEnabled: Bool
|
||||
@NSManaged var isConsoleEnabled: Bool
|
||||
|
||||
@NSManaged var isBackgroundRefreshEnabled: Bool
|
||||
@NSManaged var isDebugModeEnabled: Bool
|
||||
@NSManaged var presentedLaunchReminderNotification: Bool
|
||||
@@ -70,6 +73,8 @@ public extension UserDefaults
|
||||
let localServerSupportsRefreshing = !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14)
|
||||
|
||||
let defaults = [
|
||||
#keyPath(UserDefaults.isDevModeEnabled): false,
|
||||
#keyPath(UserDefaults.isConsoleEnabled): false,
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
|
||||
Reference in New Issue
Block a user