diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/ApplicationLibrary.swift b/iOS-SwiftUI-Code/ApplicationLibrary/ApplicationLibrary.swift
new file mode 100644
index 0000000..b8246da
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/ApplicationLibrary.swift
@@ -0,0 +1,6 @@
+import Foundation
+
+public class ApplicationLibrary {
+ public static let bundle = Bundle(for: ApplicationLibrary.self)
+ public static let inPreview = false
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/Contents.json b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/Contents.json b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/Contents.json
new file mode 100644
index 0000000..51af668
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "info": {
+ "author": "xcode",
+ "version": 1
+ },
+ "symbols": [
+ {
+ "filename": "save-save_symbol.svg",
+ "idiom": "universal"
+ }
+ ]
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/save-save_symbol.svg b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/save-save_symbol.svg
new file mode 100644
index 0000000..2b67790
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Assets.xcassets/save.symbolset/save-save_symbol.svg
@@ -0,0 +1,127 @@
+
\ No newline at end of file
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Service/ProfileUpdateTask.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Service/ProfileUpdateTask.swift
new file mode 100644
index 0000000..65ce3d7
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Service/ProfileUpdateTask.swift
@@ -0,0 +1,76 @@
+import Foundation
+import Library
+
+public enum ProfileUpdateTask {
+ static let minUpdateInterval: TimeInterval = 15 * 60
+ static let defaultUpdateInterval: TimeInterval = 60 * 60
+
+ private static var timer: Timer?
+
+ public static func configure() async throws {
+ timer?.invalidate()
+ timer = nil
+ let profiles = try await ProfileManager.listAutoUpdateEnabled()
+ if profiles.isEmpty {
+ return
+ }
+ var updateInterval = profiles.map { it in
+ it.autoUpdateIntervalOrDefault
+ }.min()!
+ if updateInterval < minUpdateInterval {
+ updateInterval = minUpdateInterval
+ }
+ timer = Timer(fire: calculateEarliestBeginDate(profiles), interval: updateInterval, repeats: true) { _ in
+ Task {
+ await getAndupdateProfiles()
+ }
+ }
+ }
+
+ static func calculateEarliestBeginDate(_ profiles: [Profile]) -> Date {
+ let nowTime = Date.now
+ var earliestBeginDate = profiles.map { it in
+ it.lastUpdated!.addingTimeInterval(it.autoUpdateIntervalOrDefault)
+ }.min()!
+ if earliestBeginDate <= nowTime {
+ earliestBeginDate = nowTime
+ }
+ return earliestBeginDate
+ }
+
+ private nonisolated static func getAndupdateProfiles() async {
+ do {
+ _ = try await updateProfiles(ProfileManager.listAutoUpdateEnabled())
+ NSLog("profile update task succeed")
+ } catch {
+ NSLog("profile update task failed: \(error.localizedDescription)")
+ }
+ }
+
+ static func updateProfiles(_ profiles: [Profile]) async -> Bool {
+ var success = true
+ for profile in profiles {
+ if profile.lastUpdated! > Date(timeIntervalSinceNow: -profile.autoUpdateIntervalOrDefault) {
+ continue
+ }
+ do {
+ try await profile.updateRemoteProfile()
+ NSLog("Updated profile \(profile.name)")
+ } catch {
+ NSLog("Update profile \(profile.name) failed: \(error.localizedDescription)")
+ success = false
+ }
+ }
+ return success
+ }
+}
+
+extension Profile {
+ var autoUpdateIntervalOrDefault: TimeInterval {
+ if autoUpdateInterval > 0 {
+ return TimeInterval(autoUpdateInterval * 60)
+ } else {
+ return ProfileUpdateTask.defaultUpdateInterval
+ }
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Service/StoreManager.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Service/StoreManager.swift
new file mode 100644
index 0000000..dce0e0d
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Service/StoreManager.swift
@@ -0,0 +1,139 @@
+//
+// File.swift
+// LoginKit
+//
+// Created by Mac on 2024/10/12.
+//
+
+import Foundation
+import Foundation
+
+//MACOS
+// 创建一个全局的 UserManager 类,负责从 UserDefaults 中读取和存储用户信息及登录状态
+class StoreManager {
+ static let shared = StoreManager()
+
+
+ public let configURL = "https://api.gooapis.com/api/vpnconfig.php"
+
+ // UserDefaults keys
+ private let loginStatusKey = "isLoggedIn"
+ private let userEmailKey = "userEmail"
+ private let avatarurlKey = "avatar_url"
+ private let autodataKey = "autodata"
+ private let subURLDataKey = "subURLData"
+ private let baseURLKey = "baseURLKey"
+
+
+ // UserDefaults 引用
+ private let defaults = UserDefaults.standard
+
+ func baseURL()-> String{
+ return UserDefaults.standard.string(forKey: "baseURLKey") ?? ""
+ }
+
+ func mainregisterURL()-> String{
+ UserDefaults.standard.string(forKey: "mainregisterURLKey") ?? ""
+ }
+
+ func paymentURL()-> String{
+ UserDefaults.standard.string(forKey: "paymentURLKey") ?? ""
+ }
+ func telegramUrl()-> String{
+ UserDefaults.standard.string(forKey: "telegramUrlKey") ?? ""
+ }
+ func kefuUrl()-> String{
+ UserDefaults.standard.string(forKey: "kefuUrlKey") ?? ""
+ }
+ func websiteURL()-> String{
+ UserDefaults.standard.string(forKey: "websiteURLKey") ?? ""
+ }
+
+ //存储 URL 相关信息
+ func storebaseURLData(data: String) {
+ defaults.set(data, forKey: "baseURLKey")
+ defaults.synchronize()
+ }
+
+ func storemainregisterURLData(data: String) {
+ defaults.set(data, forKey: "mainregisterURLKey")
+ defaults.synchronize()
+ }
+
+ func storepaymentURLData(data: String) {
+ defaults.set(data, forKey: "paymentURLKey")
+ defaults.synchronize()
+ }
+
+ func storetelegramUrlData(data: String) {
+ defaults.set(data, forKey: "telegramUrlKey")
+ defaults.synchronize()
+ }
+
+ func storekefuUrlData(data: String) {
+ defaults.set(data, forKey: "kefuUrlKey")
+ defaults.synchronize()
+ }
+ func storewebsiteURLData(data: String) {
+ defaults.set(data, forKey: "websiteURLKey")
+ defaults.synchronize()
+ }
+
+
+ // 更新登录状态
+ func updateLoginStatus(_ loggedIn: Bool) {
+ defaults.set(loggedIn, forKey: loginStatusKey)
+ defaults.synchronize()
+ }
+
+
+ // 检查是否已登录
+ func isUserLoggedIn() -> Bool {
+ return defaults.bool(forKey: loginStatusKey)
+ }
+
+ // 存储用户信息
+ func storeUserInfo(email: String, avator: String) {
+ defaults.set(email, forKey: userEmailKey)
+ defaults.set(avator, forKey: avatarurlKey) // 假设 info 是 JSON 字符串
+ defaults.synchronize()
+ }
+
+ // 读取用户信息
+ func getUserInfo() -> (email: String, avator: String) {
+ let email = defaults.string(forKey: userEmailKey) ?? ""
+ let avator = defaults.string(forKey: avatarurlKey) ?? ""
+ return (email, avator)
+ }
+
+ // 存储 autodata
+ func storeAutoData(data: String) {
+ defaults.set(data, forKey: autodataKey)
+ defaults.synchronize()
+ }
+
+ // 读取 autodata
+ func getSuburlData() -> String {
+ return defaults.string(forKey: subURLDataKey) ?? ""
+ }
+
+ // 存储 autodata
+ func storeSuburlData(data: String) {
+ defaults.set(data, forKey: subURLDataKey)
+ defaults.synchronize()
+ }
+
+ // 读取 autodata
+ func getAutoData() -> String {
+ return defaults.string(forKey: autodataKey) ?? ""
+ }
+
+ // 清除所有数据(例如在登出时)
+ func clearUserData() {
+ defaults.removeObject(forKey: loginStatusKey)
+ defaults.removeObject(forKey: userEmailKey)
+ defaults.removeObject(forKey: avatarurlKey)
+ defaults.removeObject(forKey: autodataKey)
+ defaults.removeObject(forKey: subURLDataKey)
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Service/UIProfileUpdateTask.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Service/UIProfileUpdateTask.swift
new file mode 100644
index 0000000..4fde2a7
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Service/UIProfileUpdateTask.swift
@@ -0,0 +1,79 @@
+import BackgroundTasks
+import Foundation
+import Library
+#if canImport(UIKit)
+ import UIKit
+#endif
+
+#if os(iOS) || os(tvOS)
+ public class UIProfileUpdateTask: BGAppRefreshTask {
+ private static let taskSchedulerPermittedIdentifier = "\(FilePath.packageName).update_profiles"
+
+ private static var registered = false
+ public static func configure() throws {
+ if !registered {
+ let success = BGTaskScheduler.shared.register(forTaskWithIdentifier: taskSchedulerPermittedIdentifier, using: nil) { task in
+ NSLog("profile update task started")
+ Task {
+ await UIProfileUpdateTask.getAndUpdateProfiles(task)
+ }
+ }
+ if !success {
+ throw NSError(domain: "register task failed", code: 0)
+ }
+ registered = true
+ }
+ Task {
+ BGTaskScheduler.shared.cancelAllTaskRequests()
+ let profiles = try await ProfileManager.listAutoUpdateEnabled()
+ if profiles.isEmpty {
+ return
+ }
+ try scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles))
+ }
+ Task {
+ if await UIApplication.shared.backgroundRefreshStatus != .available {
+ await updateOnce()
+ }
+ }
+ }
+
+ private nonisolated static func updateOnce() async {
+ NSLog("update profiles at start since background refresh unavailable")
+ let profiles: [Profile]
+ do {
+ profiles = try await ProfileManager.listAutoUpdateEnabled()
+ } catch {
+ return
+ }
+ if profiles.isEmpty {
+ return
+ }
+ _ = await ProfileUpdateTask.updateProfiles(profiles)
+ }
+
+ private nonisolated static func getAndUpdateProfiles(_ task: BGTask) async {
+ let profiles: [Profile]
+ do {
+ profiles = try await ProfileManager.listAutoUpdateEnabled()
+ } catch {
+ return
+ }
+ if profiles.isEmpty {
+ return
+ }
+ let success = await ProfileUpdateTask.updateProfiles(profiles)
+ try? scheduleUpdate(ProfileUpdateTask.calculateEarliestBeginDate(profiles))
+ task.setTaskCompleted(success: success)
+ task.expirationHandler = {
+ try? scheduleUpdate(nil)
+ }
+ }
+
+ private static func scheduleUpdate(_ earliestBeginDate: Date?) throws {
+ let request = BGAppRefreshTaskRequest(identifier: taskSchedulerPermittedIdentifier)
+ request.earliestBeginDate = earliestBeginDate
+ try BGTaskScheduler.shared.submit(request)
+ }
+ }
+#endif
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Alert.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Alert.swift
new file mode 100644
index 0000000..7de1feb
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Alert.swift
@@ -0,0 +1,74 @@
+import Foundation
+import SwiftUI
+
+public extension Alert {
+ init(_ error: Error, _ dismissAction: (() -> Void)? = nil) {
+ self.init(
+ errorMessage: error.localizedDescription,
+ dismissAction
+ )
+ }
+
+ init(errorMessage: String, _ dismissAction: (() -> Void)? = nil) {
+ self.init(
+ title: Text("错误提示"),
+ message: Text(errorMessage),
+ dismissButton: .default(Text("确定")) {
+ dismissAction?()
+ }
+ )
+ }
+
+ init(title: String = "提示", okMessage: String, _ dismissAction: (() -> Void)? = nil) {
+ self.init(
+ title: Text(title),
+ message: Text(okMessage),
+ dismissButton: .default(Text("确定")) {
+ dismissAction?()
+ }
+ )
+ }
+
+ init(OKORNOtitle: String = "提示", YesOrNOMessage: String, _ primaryAction: (() -> Void)? = nil) {
+ self.init(
+ title: Text(OKORNOtitle),
+ message: Text(YesOrNOMessage),
+ primaryButton: .default(Text("确定"), action: {
+ primaryAction?()
+ }),
+ secondaryButton: .cancel(Text("取消"), action: {
+ })
+ )
+ }
+
+}
+
+public extension View {
+ func alertBinding(_ binding: Binding) -> some View {
+
+ alert(isPresented: Binding(get: {
+ binding.wrappedValue != nil
+ }, set: { newValue, _ in
+ if !newValue {
+ binding.wrappedValue = nil
+ }
+ })) {
+ binding.wrappedValue!
+
+ }
+
+
+ }
+
+ func alertBinding(_ binding: Binding, _ isLoading: Binding) -> some View {
+ alert(isPresented: Binding(get: {
+ binding.wrappedValue != nil
+ }, set: { newValue, _ in
+ if !newValue, !isLoading.wrappedValue {
+ binding.wrappedValue = nil
+ }
+ })) {
+ binding.wrappedValue!
+ }
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/BackButton.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/BackButton.swift
new file mode 100644
index 0000000..afcbdeb
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/BackButton.swift
@@ -0,0 +1,14 @@
+import Foundation
+import SwiftUI
+
+public struct BackButton: View {
+ @Environment(\.dismiss) private var dismiss
+
+ public var body: some View {
+ Button {
+ dismiss()
+ } label: {
+ Image(systemName: "chevron.backward")
+ }
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Setter.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Setter.swift
new file mode 100644
index 0000000..395f4fe
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Setter.swift
@@ -0,0 +1,12 @@
+import Foundation
+import SwiftUI
+
+public extension Binding {
+ func withSetter(_ setter: @escaping (Value) -> Void) -> Binding {
+ Binding {
+ wrappedValue
+ } set: { [setter] newValue, _ in
+ setter(newValue)
+ }
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift
new file mode 100644
index 0000000..25bf0fd
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/Binding+Unwrap.swift
@@ -0,0 +1,29 @@
+import SwiftUI
+
+public extension Binding {
+ func unwrapped(_ defaultValue: T) -> Binding where Value == T? {
+ Binding(get: {
+ wrappedValue ?? defaultValue
+ }, set: { newValue in
+ wrappedValue = newValue
+ })
+ }
+}
+
+public extension Binding where Value == Int32 {
+ func stringBinding(defaultValue: Int32) -> Binding {
+ Binding {
+ var intValue = wrappedValue
+ if intValue == 0 {
+ intValue = defaultValue
+ }
+ return String(intValue)
+ } set: { newValue in
+ var newIntValue = Int32(newValue) ?? defaultValue
+ if newIntValue == 0 {
+ newIntValue = defaultValue
+ }
+ wrappedValue = newIntValue
+ }
+ }
+}
diff --git a/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/DeleteButton.swift b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/DeleteButton.swift
new file mode 100644
index 0000000..746f8c1
--- /dev/null
+++ b/iOS-SwiftUI-Code/ApplicationLibrary/Views/Abstract/DeleteButton.swift
@@ -0,0 +1,46 @@
+import Foundation
+import SwiftUI
+
+public struct DeleteButton