Files
sing-box-for-apple/ApplicationLibrary/Views/Profile/ProfileViewModel.swift
T
2025-12-01 18:22:06 +08:00

137 lines
4.6 KiB
Swift

import Foundation
import Libbox
import Library
import SwiftUI
@MainActor
public class ProfileViewModel: BaseViewModel {
@Published public var importRemoteProfileRequest: NewProfileView.ImportRequest?
@Published public var importRemoteProfilePresented = false
@Published public var isUpdating = false
@Published public var profileList: [ProfilePreview] = []
#if os(iOS) || os(tvOS)
@Published public var editMode = EditMode.inactive
#endif
private weak var environments: ExtensionEnvironments?
override public init() {
super.init()
isLoading = true
}
public func setEnvironments(_ environments: ExtensionEnvironments) {
self.environments = environments
}
public func createImportProfileDialog(_ profile: LibboxProfileContent) {
alert = AlertState(
title: String(localized: "Import Profile"),
message: String(localized: "Are you sure to import profile \(profile.name)?"),
primaryButton: .default(String(localized: "Import")) {
Task {
do {
try await profile.importProfile()
} catch {
self.alert = AlertState(error: error)
return
}
await self.doReload()
self.environments?.emptyProfiles = self.profileList.isEmpty
}
},
secondaryButton: .cancel()
)
}
public func createImportRemoteProfileDialog(_ newValue: LibboxImportRemoteProfile) {
importRemoteProfileRequest = .init(name: newValue.name, url: newValue.url)
alert = AlertState(
title: String(localized: "Import Remote Profile"),
message: String(localized: "Are you sure to import remote profile \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
primaryButton: .default(String(localized: "Import")) {
self.importRemoteProfilePresented = true
},
secondaryButton: .cancel()
)
}
public func doReload() async {
defer {
isLoading = false
}
if ApplicationLibrary.inPreview {
profileList = [
ProfilePreview(Profile(id: 0, name: "profile local", type: .local, path: "")),
ProfilePreview(Profile(id: 1, name: "profile remote", type: .remote, path: "", lastUpdated: Date(timeIntervalSince1970: 0))),
]
} else {
do {
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
} catch {
alert = AlertState(error: error)
return
}
}
environments?.emptyProfiles = profileList.isEmpty
}
public func updateProfile(_ profile: Profile) async {
await updateProfileBackground(profile)
isUpdating = false
}
private nonisolated func updateProfileBackground(_ profile: Profile) async {
do {
_ = try await profile.updateRemoteProfile()
} catch {
await MainActor.run {
alert = AlertState(error: error)
}
}
}
public func deleteProfile(_ profile: Profile) async {
do {
_ = try await ProfileManager.delete(profile)
environments?.profileUpdate.send()
environments?.emptyProfiles = profileList.isEmpty
} catch {
alert = AlertState(error: error)
}
}
public func moveProfile(from source: IndexSet, to destination: Int) {
profileList.move(fromOffsets: source, toOffset: destination)
for (index, profile) in profileList.enumerated() {
profileList[index].order = UInt32(index)
profile.origin.order = UInt32(index)
}
Task {
do {
try await ProfileManager.update(profileList.map(\.origin))
environments?.profileUpdate.send()
} catch {
alert = AlertState(error: error)
}
}
}
public func deleteProfile(where profileIndex: IndexSet) {
let profileToDelete = profileIndex.map { index in
profileList[index].origin
}
profileList.remove(atOffsets: profileIndex)
Task {
do {
_ = try await ProfileManager.delete(profileToDelete)
environments?.emptyProfiles = profileList.isEmpty
environments?.profileUpdate.send()
} catch {
alert = AlertState(error: error)
}
}
}
}