mirror of
https://github.com/wibus-wee/InjectGUI.git
synced 2025-11-25 19:37:33 +08:00
feat: Configuration & InjectConfiguraion
This commit is contained in:
@@ -11,6 +11,12 @@
|
||||
38877A1F2C4A6F83009F5910 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A1E2C4A6F83009F5910 /* ContentView.swift */; };
|
||||
38877A212C4A6F87009F5910 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38877A202C4A6F87009F5910 /* Assets.xcassets */; };
|
||||
38877A252C4A6F87009F5910 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 38877A242C4A6F87009F5910 /* Preview Assets.xcassets */; };
|
||||
38877A2E2C4A6FFA009F5910 /* InjectConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A2D2C4A6FFA009F5910 /* InjectConfiguration.swift */; };
|
||||
38877A302C4A70DB009F5910 /* SoftwareManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A2F2C4A70DB009F5910 /* SoftwareManager.swift */; };
|
||||
38877A332C4A7222009F5910 /* PublishedStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A322C4A7222009F5910 /* PublishedStorage.swift */; };
|
||||
38877A352C4A7254009F5910 /* ViewKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A342C4A7254009F5910 /* ViewKit.swift */; };
|
||||
38877A372C4A7294009F5910 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A362C4A7294009F5910 /* Configuration.swift */; };
|
||||
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A392C4A730F009F5910 /* Constants.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -20,6 +26,12 @@
|
||||
38877A202C4A6F87009F5910 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
38877A222C4A6F87009F5910 /* InjectGUI.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = InjectGUI.entitlements; sourceTree = "<group>"; };
|
||||
38877A242C4A6F87009F5910 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
38877A2D2C4A6FFA009F5910 /* InjectConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectConfiguration.swift; sourceTree = "<group>"; };
|
||||
38877A2F2C4A70DB009F5910 /* SoftwareManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareManager.swift; sourceTree = "<group>"; };
|
||||
38877A322C4A7222009F5910 /* PublishedStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedStorage.swift; sourceTree = "<group>"; };
|
||||
38877A342C4A7254009F5910 /* ViewKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewKit.swift; sourceTree = "<group>"; };
|
||||
38877A362C4A7294009F5910 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
38877A392C4A730F009F5910 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -52,8 +64,10 @@
|
||||
38877A1B2C4A6F83009F5910 /* InjectGUI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
38877A1C2C4A6F83009F5910 /* InjectGUIApp.swift */,
|
||||
38877A1E2C4A6F83009F5910 /* ContentView.swift */,
|
||||
38877A382C4A7306009F5910 /* App */,
|
||||
38877A312C4A7217009F5910 /* Extension */,
|
||||
38877A2C2C4A6FD7009F5910 /* View */,
|
||||
38877A2B2C4A6FCB009F5910 /* Backend */,
|
||||
38877A202C4A6F87009F5910 /* Assets.xcassets */,
|
||||
38877A222C4A6F87009F5910 /* InjectGUI.entitlements */,
|
||||
38877A232C4A6F87009F5910 /* Preview Content */,
|
||||
@@ -69,6 +83,42 @@
|
||||
path = "Preview Content";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
38877A2B2C4A6FCB009F5910 /* Backend */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
38877A2D2C4A6FFA009F5910 /* InjectConfiguration.swift */,
|
||||
38877A2F2C4A70DB009F5910 /* SoftwareManager.swift */,
|
||||
38877A362C4A7294009F5910 /* Configuration.swift */,
|
||||
);
|
||||
path = Backend;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
38877A2C2C4A6FD7009F5910 /* View */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
38877A1E2C4A6F83009F5910 /* ContentView.swift */,
|
||||
);
|
||||
path = View;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
38877A312C4A7217009F5910 /* Extension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
38877A322C4A7222009F5910 /* PublishedStorage.swift */,
|
||||
38877A342C4A7254009F5910 /* ViewKit.swift */,
|
||||
);
|
||||
path = Extension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
38877A382C4A7306009F5910 /* App */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
38877A1C2C4A6F83009F5910 /* InjectGUIApp.swift */,
|
||||
38877A392C4A730F009F5910 /* Constants.swift */,
|
||||
);
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -141,6 +191,12 @@
|
||||
files = (
|
||||
38877A1F2C4A6F83009F5910 /* ContentView.swift in Sources */,
|
||||
38877A1D2C4A6F83009F5910 /* InjectGUIApp.swift in Sources */,
|
||||
38877A372C4A7294009F5910 /* Configuration.swift in Sources */,
|
||||
38877A302C4A70DB009F5910 /* SoftwareManager.swift in Sources */,
|
||||
38877A332C4A7222009F5910 /* PublishedStorage.swift in Sources */,
|
||||
38877A2E2C4A6FFA009F5910 /* InjectConfiguration.swift in Sources */,
|
||||
38877A352C4A7254009F5910 /* ViewKit.swift in Sources */,
|
||||
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -7,8 +7,12 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
let configuration = Configuration.shared
|
||||
let injectConfiguration = InjectConfiguration.shared
|
||||
|
||||
@main
|
||||
struct InjectGUIApp: App {
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView()
|
||||
47
InjectGUI/Backend/Configuration.swift
Normal file
47
InjectGUI/Backend/Configuration.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Configuration.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
|
||||
class Configuration: NSObject, ObservableObject {
|
||||
static let shared = Configuration()
|
||||
var cancellable: Set<AnyCancellable> = .init()
|
||||
|
||||
override private init() {
|
||||
super.init()
|
||||
print("[I] Configuration inited.")
|
||||
objectWillChange
|
||||
.sink { _ in
|
||||
self.save()
|
||||
}
|
||||
.store(in: &cancellable)
|
||||
}
|
||||
|
||||
@PublishedStorage(key: "\(Constants.appKey).remoteGit", defaultValue: "https://github.com/QiuChenly/InjectLib")
|
||||
var remoteGit: String
|
||||
|
||||
@PublishedStorage(key: "\(Constants.appKey).remoteGitCommit", defaultValue: nil)
|
||||
var remoteGitCommit: String?
|
||||
|
||||
@PublishedStorage(key: "\(Constants.appKey).remoteGitBranch", defaultValue: nil)
|
||||
var remoteGitBranch: String?
|
||||
|
||||
|
||||
|
||||
public func save() {
|
||||
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(saveNow), object: nil)
|
||||
perform(#selector(saveNow), with: nil, afterDelay: 1)
|
||||
}
|
||||
|
||||
@objc func saveNow() {
|
||||
DispatchQueue.global().async {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
231
InjectGUI/Backend/InjectConfiguration.swift
Normal file
231
InjectGUI/Backend/InjectConfiguration.swift
Normal file
@@ -0,0 +1,231 @@
|
||||
//
|
||||
// InjectConfiguration.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - InjectConfigurationModel
|
||||
struct InjectConfigurationModel: Codable {
|
||||
let project, author: String
|
||||
let version: Double
|
||||
let description: Description
|
||||
let basePublicConfig: BasePublicConfig
|
||||
let appList: [AppList]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case project
|
||||
case author = "Author"
|
||||
case version = "Version"
|
||||
case description = "Description"
|
||||
case basePublicConfig
|
||||
case appList = "AppList"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AppList
|
||||
struct AppList: Codable {
|
||||
let packageName: PackageName
|
||||
let appBaseLocate, bridgeFile, injectFile: String?
|
||||
let needCopyToAppDir, noSignTarget, autoHandleHelper: Bool?
|
||||
let helperFile: HelperFile?
|
||||
let tccutil: Tccutil?
|
||||
let forQiuChenly, onlysh: Bool?
|
||||
let extraShell, smExtra: String?
|
||||
let componentApp: [String]?
|
||||
let deepSignApp, noDeep: Bool?
|
||||
let entitlements: String?
|
||||
let useOptool, autoHandleSetapp: Bool?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case packageName, appBaseLocate, bridgeFile, injectFile, needCopyToAppDir, noSignTarget, autoHandleHelper, helperFile, tccutil, forQiuChenly, onlysh, extraShell
|
||||
case smExtra = "SMExtra"
|
||||
case componentApp, deepSignApp, noDeep, entitlements, useOptool, autoHandleSetapp
|
||||
}
|
||||
}
|
||||
|
||||
enum HelperFile: Codable {
|
||||
case string(String)
|
||||
case stringArray([String])
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if let x = try? container.decode([String].self) {
|
||||
self = .stringArray(x)
|
||||
return
|
||||
}
|
||||
if let x = try? container.decode(String.self) {
|
||||
self = .string(x)
|
||||
return
|
||||
}
|
||||
throw DecodingError.typeMismatch(HelperFile.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for HelperFile"))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .string(let x):
|
||||
try container.encode(x)
|
||||
case .stringArray(let x):
|
||||
try container.encode(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum PackageName: Codable {
|
||||
case string(String)
|
||||
case stringArray([String])
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if let x = try? container.decode([String].self) {
|
||||
self = .stringArray(x)
|
||||
return
|
||||
}
|
||||
if let x = try? container.decode(String.self) {
|
||||
self = .string(x)
|
||||
return
|
||||
}
|
||||
throw DecodingError.typeMismatch(PackageName.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for PackageName"))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .string(let x):
|
||||
try container.encode(x)
|
||||
case .stringArray(let x):
|
||||
try container.encode(x)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var allStrings: [String] {
|
||||
switch self {
|
||||
case .string(let x):
|
||||
return [x]
|
||||
case .stringArray(let x):
|
||||
return x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Tccutil: Codable {
|
||||
case bool(Bool)
|
||||
case stringArray([String])
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
if let x = try? container.decode(Bool.self) {
|
||||
self = .bool(x)
|
||||
return
|
||||
}
|
||||
if let x = try? container.decode([String].self) {
|
||||
self = .stringArray(x)
|
||||
return
|
||||
}
|
||||
throw DecodingError.typeMismatch(Tccutil.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Tccutil"))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
switch self {
|
||||
case .bool(let x):
|
||||
try container.encode(x)
|
||||
case .stringArray(let x):
|
||||
try container.encode(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - BasePublicConfig
|
||||
struct BasePublicConfig: Codable {
|
||||
let bridgeFile: String
|
||||
}
|
||||
|
||||
// MARK: - Description
|
||||
struct Description: Codable {
|
||||
let desc, bridgeFile, packageName, injectFile: String
|
||||
let supportVersion, supportSubVersion, extraShell, needCopyToAppDir: String
|
||||
let deepSignApp, disableLibraryValidate, entitlements, noSignTarget: String
|
||||
let noDeep, tccutil, autoHandleSetapp, autoHandleHelper: String
|
||||
let helperFile, componentApp, forQiuChenly: String
|
||||
}
|
||||
|
||||
class InjectConfiguration {
|
||||
static let shared = InjectConfiguration()
|
||||
var remoteConf = nil as InjectConfigurationModel?
|
||||
private init() {
|
||||
updateRemoteConf()
|
||||
}
|
||||
|
||||
private func downloadConfig(data: Data?) {
|
||||
let decoder = JSONDecoder()
|
||||
let conf = try! decoder.decode(InjectConfigurationModel.self, from: data!)
|
||||
remoteConf = conf
|
||||
#if DEBUG
|
||||
print("[I] Downloaded config.json")
|
||||
print(conf)
|
||||
#endif
|
||||
}
|
||||
|
||||
/// 更新远程配置
|
||||
func updateRemoteConf() {
|
||||
let url = configuration.remoteGit
|
||||
let commit = configuration.remoteGitCommit
|
||||
let branch = configuration.remoteGitBranch
|
||||
// <url>/raw/<branch or commit>/config.json
|
||||
let _url = "\(url)/raw/\(branch ?? commit ?? "main")/config.json"
|
||||
let dataUrl = URL(string: _url)!
|
||||
|
||||
let task = URLSession.shared.dataTask(with: dataUrl) { data, response, error in
|
||||
if let error = error {
|
||||
print("[W] Failed to download config.json: \(error.localizedDescription)")
|
||||
return
|
||||
}
|
||||
self.downloadConfig(data: data)
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
|
||||
/// 设置远程配置来源
|
||||
func customRemoteConf(url: String, commit: String?, branch: String?) {
|
||||
configuration.remoteGit = url
|
||||
configuration.remoteGitBranch = branch
|
||||
configuration.remoteGitCommit = commit
|
||||
updateRemoteConf()
|
||||
}
|
||||
|
||||
/// 获取当前配置支持的 Package
|
||||
func getSupportedPackages() -> [String] {
|
||||
guard let conf = remoteConf else {
|
||||
return []
|
||||
}
|
||||
return conf.appList.flatMap { $0.packageName.allStrings }
|
||||
}
|
||||
|
||||
func downloadInjectLib() {
|
||||
|
||||
}
|
||||
|
||||
func updateInjectLib() {
|
||||
|
||||
}
|
||||
|
||||
func update() {
|
||||
updateInjectLib()
|
||||
updateRemoteConf()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// injectConfiguration.getSupportedPackages()
|
||||
// injectConfiguration.injectDetail(package: "com.xxx.xxx")
|
||||
// injectConfiguration.customRemoteConf(url: "", commit: nil)
|
||||
// injectConfiguration.updateRemoteConf()
|
||||
// injectConfiguration.downloadInjectLib()
|
||||
// injectConfiguration.updateInjectLib()
|
||||
// injectConfiguration.update()
|
||||
9
InjectGUI/Backend/SoftwareManager.swift
Normal file
9
InjectGUI/Backend/SoftwareManager.swift
Normal file
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// SoftwareManager.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
Image(systemName: "globe")
|
||||
.imageScale(.large)
|
||||
.foregroundColor(.accentColor)
|
||||
Text("Hello, world!")
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ContentView()
|
||||
}
|
||||
}
|
||||
95
InjectGUI/Extension/PublishedStorage.swift
Normal file
95
InjectGUI/Extension/PublishedStorage.swift
Normal file
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// PublishedStorage.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by Lakr Aream on 2023/6/5.
|
||||
// Copyright © 2023 Lakr Aream. All rights reserved.
|
||||
//
|
||||
|
||||
// Remove SwiftUI stuff, use Combine & Foundation only.
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
private let encoder = JSONEncoder()
|
||||
private let decoder = JSONDecoder()
|
||||
|
||||
@propertyWrapper
|
||||
public struct PublishedStorage<ValueA: Codable> {
|
||||
@CodableDefault private var storedValue: ValueA
|
||||
|
||||
public let subject: CurrentValueSubject<ValueA, Never>
|
||||
public var defaultValue: ValueA {
|
||||
_storedValue.defaultValue
|
||||
}
|
||||
|
||||
@available(*, unavailable, message: "accessing wrappedValue will result undefined behavior")
|
||||
// PublishedStorage only accept to work inside class, which class confirms to ObservableObject
|
||||
public var wrappedValue: ValueA {
|
||||
get { subject.value }
|
||||
set { storedValue = newValue }
|
||||
}
|
||||
|
||||
public static subscript<EnclosingSelf: ObservableObject>(
|
||||
_enclosingInstance object: EnclosingSelf,
|
||||
wrapped _: ReferenceWritableKeyPath<EnclosingSelf, ValueA>,
|
||||
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, PublishedStorage<ValueA>>
|
||||
) -> ValueA {
|
||||
get {
|
||||
object[keyPath: storageKeyPath].subject.value
|
||||
}
|
||||
set {
|
||||
(object.objectWillChange as? ObservableObjectPublisher)?.send()
|
||||
object[keyPath: storageKeyPath].subject.send(newValue)
|
||||
object[keyPath: storageKeyPath].storedValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
init(key: String, defaultValue: ValueA, storage: UserDefaults = .standard) {
|
||||
let storageCore = CodableDefault(key: key, defaultValue: defaultValue, storage: storage)
|
||||
_storedValue = storageCore
|
||||
subject = .init(storageCore.wrappedValue)
|
||||
}
|
||||
|
||||
public func saveFromSubjectValueImmediately() {
|
||||
_storedValue.save(value: subject.value)
|
||||
}
|
||||
}
|
||||
|
||||
private extension PublishedStorage {
|
||||
@propertyWrapper
|
||||
struct CodableDefault<ValueB: Codable> {
|
||||
let key: String
|
||||
let defaultValue: ValueB
|
||||
var storage: UserDefaults = .standard
|
||||
|
||||
public init(key: String, defaultValue: ValueB, storage: UserDefaults = .standard) {
|
||||
self.key = key
|
||||
self.defaultValue = defaultValue
|
||||
self.storage = storage
|
||||
}
|
||||
|
||||
public var wrappedValue: ValueB {
|
||||
get {
|
||||
if let read = storage.value(forKey: key) as? Data,
|
||||
let object = try? decoder.decode(ValueB.self, from: read)
|
||||
{
|
||||
return object
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
set {
|
||||
save(value: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
public func save(value: ValueB) {
|
||||
do {
|
||||
let data = try encoder.encode(value)
|
||||
storage.setValue(data, forKey: key)
|
||||
return
|
||||
} catch {}
|
||||
storage.setValue(nil, forKey: key)
|
||||
}
|
||||
}
|
||||
}
|
||||
178
InjectGUI/Extension/ViewKit.swift
Normal file
178
InjectGUI/Extension/ViewKit.swift
Normal file
@@ -0,0 +1,178 @@
|
||||
//
|
||||
// ViewKit.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum InternalKit {
|
||||
typealias IsClickPrimaryButton = Bool
|
||||
|
||||
struct AnySheetView: View {
|
||||
@Environment(\.dismiss) var dismiss // dismiss the sheet
|
||||
let title: String
|
||||
let secondaryButton: String
|
||||
let primaryButton: String
|
||||
let toolbar: (() -> (AnyView))?
|
||||
let content: (() -> (AnyView))
|
||||
let action: (IsClickPrimaryButton) -> Void
|
||||
|
||||
init(
|
||||
title: String,
|
||||
secondaryButton: String,
|
||||
primaryButton: String,
|
||||
toolbar: (() -> (AnyView))? = nil,
|
||||
content: @escaping () -> AnyView,
|
||||
action: @escaping (IsClickPrimaryButton) -> Void
|
||||
) {
|
||||
self.title = title
|
||||
self.secondaryButton = secondaryButton
|
||||
self.primaryButton = primaryButton
|
||||
self.toolbar = toolbar
|
||||
self.content = content
|
||||
self.action = action
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
if title.isEmpty, toolbar == nil {
|
||||
} else {
|
||||
HStack {
|
||||
Text(title).font(.headline)
|
||||
Spacer()
|
||||
if let toolbar { toolbar() }
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
content().frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
Divider()
|
||||
HStack {
|
||||
if !secondaryButton.isEmpty {
|
||||
Button { action(false) } label: { Text(secondaryButton) }
|
||||
}
|
||||
Spacer()
|
||||
if !primaryButton.isEmpty {
|
||||
Button { action(true) } label: { Text(primaryButton) }
|
||||
.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
.background(
|
||||
Button("") { dismiss() }
|
||||
.keyboardShortcut(.cancelAction)
|
||||
.opacity(0)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
static func useSheet(
|
||||
title: String,
|
||||
secondaryButton: String = "Cancel",
|
||||
primaryButton: String = "OK",
|
||||
toolbar: (() -> (AnyView))? = nil,
|
||||
content: @escaping () -> AnyView,
|
||||
action: @escaping (IsClickPrimaryButton) -> Void?
|
||||
) -> some View {
|
||||
AnySheetView(
|
||||
title: title,
|
||||
secondaryButton: secondaryButton,
|
||||
primaryButton: primaryButton,
|
||||
toolbar: toolbar,
|
||||
content: content,
|
||||
action: { isClickPrimaryButton in
|
||||
action(isClickPrimaryButton)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
static func useAlert(
|
||||
title: String,
|
||||
message: String,
|
||||
primaryButton: String = "OK",
|
||||
secondaryButton: String = "Cancel",
|
||||
action: @escaping (IsClickPrimaryButton) -> Void?
|
||||
) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = title
|
||||
alert.informativeText = message
|
||||
alert.addButton(withTitle: primaryButton)
|
||||
alert.addButton(withTitle: secondaryButton)
|
||||
alert.alertStyle = .informational
|
||||
alert.beginSheetModal(for: NSApp.mainWindow!) { response in
|
||||
action(response == .alertFirstButtonReturn)
|
||||
}
|
||||
}
|
||||
|
||||
static func eazyAlert(
|
||||
title: String,
|
||||
message: String
|
||||
) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = title
|
||||
alert.informativeText = message
|
||||
alert.alertStyle = .informational
|
||||
alert.beginSheetModal(for: NSApp.mainWindow!) { response in
|
||||
}
|
||||
}
|
||||
|
||||
static func useFilePanel(
|
||||
title: String,
|
||||
message: String,
|
||||
primaryButton: String = "OK",
|
||||
secondaryButton: String = "Cancel",
|
||||
action: @escaping (URL?) -> Void
|
||||
) {
|
||||
let panel = NSOpenPanel()
|
||||
panel.title = title
|
||||
panel.message = message
|
||||
panel.prompt = primaryButton
|
||||
panel.canChooseFiles = true
|
||||
panel.canChooseDirectories = false
|
||||
panel.canCreateDirectories = false
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.beginSheetModal(for: NSApp.mainWindow!) { response in
|
||||
action(panel.url)
|
||||
}
|
||||
}
|
||||
|
||||
static func useDirectoryPanel(
|
||||
title: String,
|
||||
message: String,
|
||||
primaryButton: String = "OK",
|
||||
secondaryButton: String = "Cancel",
|
||||
action: @escaping (URL?) -> Void
|
||||
) {
|
||||
let panel = NSOpenPanel()
|
||||
panel.title = title
|
||||
panel.message = message
|
||||
panel.prompt = primaryButton
|
||||
panel.canChooseFiles = false
|
||||
panel.canChooseDirectories = true
|
||||
panel.canCreateDirectories = true
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.beginSheetModal(for: NSApp.mainWindow!) { response in
|
||||
action(panel.url)
|
||||
}
|
||||
}
|
||||
|
||||
static func saveFilePanel(
|
||||
title: String,
|
||||
message: String,
|
||||
primaryButton: String = "OK",
|
||||
secondaryButton: String = "Cancel",
|
||||
action: @escaping (URL?) -> Void
|
||||
) {
|
||||
let panel = NSSavePanel()
|
||||
panel.title = title
|
||||
panel.message = message
|
||||
panel.prompt = primaryButton
|
||||
panel.canCreateDirectories = true
|
||||
panel.beginSheetModal(for: NSApp.mainWindow!) { response in
|
||||
action(panel.url)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
49
InjectGUI/View/ContentView.swift
Normal file
49
InjectGUI/View/ContentView.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// InjectGUI
|
||||
//
|
||||
// Created by wibus on 2024/7/19.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
||||
VStack {
|
||||
|
||||
Text("InjectGUI")
|
||||
.font(.largeTitle)
|
||||
|
||||
Text("remoteGit: \(configuration.remoteGit)")
|
||||
Text("remoteGitBranch: \(configuration.remoteGitBranch ?? "nil")")
|
||||
Text("remoteGitCommit: \(configuration.remoteGitCommit ?? "nil")")
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("updateRemoteConf") {
|
||||
injectConfiguration.updateRemoteConf()
|
||||
}
|
||||
Button("getSupportedPackages") {
|
||||
print(injectConfiguration.getSupportedPackages())
|
||||
}
|
||||
|
||||
Button("downloadInjectLib") {
|
||||
injectConfiguration.downloadInjectLib()
|
||||
}
|
||||
Button("updateInjectLib") {
|
||||
injectConfiguration.updateInjectLib()
|
||||
}
|
||||
Button("update") {
|
||||
injectConfiguration.update()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user