Prepare airdrop support
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
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("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok")) {
|
||||
dismissAction?()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func alertBinding(_ binding: Binding<Alert?>) -> some View {
|
||||
alert(isPresented: Binding(get: {
|
||||
binding.wrappedValue != nil
|
||||
}, set: { newValue, _ in
|
||||
if !newValue {
|
||||
binding.wrappedValue = nil
|
||||
}
|
||||
})) {
|
||||
binding.wrappedValue!
|
||||
}
|
||||
}
|
||||
|
||||
func alertBinding(_ binding: Binding<Alert?>, _ isLoading: Binding<Bool>) -> some View {
|
||||
alert(isPresented: Binding(get: {
|
||||
binding.wrappedValue != nil
|
||||
}, set: { newValue, _ in
|
||||
if !newValue, !isLoading.wrappedValue {
|
||||
binding.wrappedValue = nil
|
||||
}
|
||||
})) {
|
||||
binding.wrappedValue!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,7 @@ public struct ActiveDashboardView: View {
|
||||
@State private var selectedProfileID: Int64!
|
||||
@State private var reasserting = false
|
||||
@State private var observer: Any?
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -78,43 +76,37 @@ public struct ActiveDashboardView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
#if os(iOS)
|
||||
.onChange(of: scenePhase, perform: { newValue in
|
||||
if newValue == .active {
|
||||
Task.detached {
|
||||
await doReload()
|
||||
}
|
||||
}
|
||||
})
|
||||
.onChange(of: selection.wrappedValue, perform: { newValue in
|
||||
if newValue == .dashboard {
|
||||
Task.detached {
|
||||
await doReload()
|
||||
}
|
||||
}
|
||||
})
|
||||
#elseif os(macOS)
|
||||
.onAppear {
|
||||
if observer == nil {
|
||||
observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in
|
||||
.onChange(of: scenePhase, perform: { newValue in
|
||||
if newValue == .active {
|
||||
Task.detached {
|
||||
await doReload()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.onChange(of: selection.wrappedValue, perform: { newValue in
|
||||
if newValue == .dashboard {
|
||||
Task.detached {
|
||||
await doReload()
|
||||
}
|
||||
}
|
||||
})
|
||||
#elseif os(macOS)
|
||||
.onAppear {
|
||||
if observer == nil {
|
||||
observer = NotificationCenter.default.addObserver(forName: ActiveDashboardView.NotificationUpdateSelectedProfile, object: nil, queue: nil, using: { _ in
|
||||
Task.detached {
|
||||
await doReload()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if let observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
.onDisappear {
|
||||
if let observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -132,8 +124,7 @@ public struct ActiveDashboardView: View {
|
||||
do {
|
||||
profileList = try ProfileManager.list()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
if profileList.isEmpty {
|
||||
@@ -158,8 +149,7 @@ public struct ActiveDashboardView: View {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.serviceReload()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
reasserting = false
|
||||
|
||||
@@ -7,8 +7,7 @@ public struct ExtensionStatusView: View {
|
||||
@State private var message: LibboxStatusMessage?
|
||||
@State private var connectTask: Task<Void, Error>?
|
||||
@State private var columnCount: Int = 4
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
private let infoFont = Font.system(.caption, design: .monospaced)
|
||||
|
||||
@@ -67,13 +66,7 @@ public struct ExtensionStatusView: View {
|
||||
}
|
||||
commandClient = nil
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
private func doReload() {
|
||||
@@ -125,8 +118,7 @@ public struct ExtensionStatusView: View {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)?.closeConnections()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ import SwiftUI
|
||||
public struct InstallProfileButton: View {
|
||||
@Environment(\.extensionProfile) private var extensionProfile
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -15,21 +14,14 @@ public struct InstallProfileButton: View {
|
||||
await installProfile()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
private func installProfile() async {
|
||||
do {
|
||||
try await ExtensionProfile.install()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct InstallSystemExtensionButton: View {
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
private let callback: () -> Void
|
||||
public init(_ callback: @escaping () -> Void) {
|
||||
self.callback = callback
|
||||
@@ -17,27 +16,19 @@
|
||||
await installSystemExtension()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
private func installSystemExtension() async {
|
||||
do {
|
||||
if let result = try await SystemExtension.install() {
|
||||
if result == .willCompleteAfterReboot {
|
||||
errorMessage = "Need reboot"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Need Reboot")
|
||||
}
|
||||
}
|
||||
callback()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ public struct StartStopButton: View {
|
||||
private struct Button0: View {
|
||||
@Environment(\.logClient) private var logClient
|
||||
@ObservedObject private var profile: ExtensionProfile
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
init(_ profile: ExtensionProfile) {
|
||||
self.profile = profile
|
||||
@@ -75,13 +74,7 @@ public struct StartStopButton: View {
|
||||
#endif
|
||||
}
|
||||
.disabled(!profile.status.isEnabled)
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
private func switchProfile(_ isEnabled: Bool) async {
|
||||
@@ -93,8 +86,7 @@ public struct StartStopButton: View {
|
||||
profile.stop()
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
@@ -54,4 +55,17 @@ public extension EnvironmentValues {
|
||||
self[logClientKey.self] = newValue
|
||||
}
|
||||
}
|
||||
|
||||
private struct importRemoteProfileKey: EnvironmentKey {
|
||||
static var defaultValue: Binding<LibboxImportRemoteProfile?> = .constant(nil)
|
||||
}
|
||||
|
||||
var importRemoteProfile: Binding<LibboxImportRemoteProfile?> {
|
||||
get {
|
||||
self[importRemoteProfileKey.self]
|
||||
}
|
||||
set {
|
||||
self[importRemoteProfileKey.self] = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,13 @@ import Library
|
||||
import SwiftUI
|
||||
|
||||
public struct GroupView: View {
|
||||
private var expland: Binding<Bool>
|
||||
@Binding private var expland: Bool
|
||||
@State private var group: OutboundGroup
|
||||
@State private var geometryWidth: CGFloat = 300
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
|
||||
public init(_ group: OutboundGroup, _ expland: Binding<Bool>) {
|
||||
self.group = group
|
||||
self.expland = expland
|
||||
_expland = expland
|
||||
}
|
||||
|
||||
private var title: some View {
|
||||
@@ -28,9 +25,9 @@ public struct GroupView: View {
|
||||
.background(Color.gray.opacity(0.5))
|
||||
.cornerRadius(4)
|
||||
Button {
|
||||
expland.wrappedValue = !expland.wrappedValue
|
||||
expland = !expland
|
||||
} label: {
|
||||
if expland.wrappedValue {
|
||||
if expland {
|
||||
Image(systemName: "arrow.down.to.line")
|
||||
} else {
|
||||
Image(systemName: "arrow.up.to.line")
|
||||
@@ -51,18 +48,11 @@ public struct GroupView: View {
|
||||
#endif
|
||||
Spacer(minLength: 6)
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
Section {
|
||||
if expland.wrappedValue {
|
||||
if expland {
|
||||
LazyVGrid(columns: Array(repeating: GridItem(.flexible()),
|
||||
count: explandColumnCount()))
|
||||
{
|
||||
@@ -123,12 +113,7 @@ public struct GroupView: View {
|
||||
}
|
||||
|
||||
private func doURLTest() {
|
||||
do {
|
||||
try LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.urlTest(group.tag)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
}
|
||||
try? LibboxNewStandaloneCommandClient(FilePath.sharedDirectory.relativePath)!.urlTest(group.tag)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,7 @@ public struct EditProfileContentView: View {
|
||||
@State private var profile: Profile!
|
||||
@State private var profileContent: String = ""
|
||||
@State private var isChanged = false
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var fatalError = false
|
||||
@State private var alert: Alert?
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
@@ -60,17 +57,7 @@ public struct EditProfileContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"), action: {
|
||||
if fatalError {
|
||||
dismiss()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.navigationTitle(navigationTitle)
|
||||
#if os(macOS)
|
||||
.toolbar {
|
||||
@@ -115,9 +102,7 @@ public struct EditProfileContentView: View {
|
||||
do {
|
||||
try loadContent0()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
fatalError = true
|
||||
errorPresented = true
|
||||
alert = Alert(error, dismiss.callAsFunction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +125,7 @@ public struct EditProfileContentView: View {
|
||||
do {
|
||||
try profile.write(profileContent)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
isChanged = false
|
||||
|
||||
@@ -10,8 +10,7 @@ public struct EditProfileView: View {
|
||||
|
||||
@State private var isLoading = false
|
||||
@State private var isChanged = false
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -69,6 +68,17 @@ public struct EditProfileView: View {
|
||||
}
|
||||
.disabled(isLoading)
|
||||
}
|
||||
if #available(iOS 16.0, *) {
|
||||
ShareLink(item: profile.shareLink) {
|
||||
Text("Share")
|
||||
}
|
||||
} else {
|
||||
Button("Share") {
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
|
||||
windowScene.keyWindow?.rootViewController?.present(UIActivityViewController(activityItems: [profile.shareLink], applicationActivities: nil), animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -132,13 +142,7 @@ public struct EditProfileView: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.navigationTitle("Edit Profile")
|
||||
}
|
||||
|
||||
@@ -150,8 +154,7 @@ public struct EditProfileView: View {
|
||||
try await Task.sleep(nanoseconds: UInt64(100 * Double(NSEC_PER_MSEC)))
|
||||
try profile.updateRemoteProfile()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,8 +162,7 @@ public struct EditProfileView: View {
|
||||
do {
|
||||
_ = try ProfileManager.update(profile)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
isChanged = false
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
@@ -16,8 +15,7 @@ import SwiftUI
|
||||
|
||||
@State private var isLoading = true
|
||||
@State private var profile: Profile!
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public var body: some View {
|
||||
viewBuilder {
|
||||
@@ -27,19 +25,11 @@ import SwiftUI
|
||||
await doReload()
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"), action: {
|
||||
dismiss()
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
EditProfileView().environmentObject(profile!)
|
||||
}
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.onExitCommand {
|
||||
dismiss()
|
||||
}
|
||||
@@ -47,20 +37,17 @@ import SwiftUI
|
||||
|
||||
private func doReload() async {
|
||||
guard let profileID else {
|
||||
errorMessage = "Context destroyed"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Context destroyed")
|
||||
return
|
||||
}
|
||||
do {
|
||||
profile = try ProfileManager.get(profileID)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
if profile == nil {
|
||||
errorMessage = "Profile deleted"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Profile deleted")
|
||||
return
|
||||
}
|
||||
isLoading = false
|
||||
|
||||
@@ -17,12 +17,21 @@ public struct NewProfileView: View {
|
||||
@State private var fileURL: URL!
|
||||
@State private var remotePath = ""
|
||||
@State private var pickerPresented = false
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public struct ImportRequest: Codable, Hashable {
|
||||
public let name: String
|
||||
public let url: String
|
||||
}
|
||||
|
||||
private let callback: (() -> Void)?
|
||||
public init(_ callback: (() -> Void)? = nil) {
|
||||
public init(_ importRequest: ImportRequest? = nil, _ callback: (() -> Void)? = nil) {
|
||||
self.callback = callback
|
||||
if let importRequest {
|
||||
_profileName = .init(initialValue: importRequest.name)
|
||||
_profileType = .init(initialValue: .remote)
|
||||
_remotePath = .init(initialValue: importRequest.url)
|
||||
}
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
@@ -88,13 +97,7 @@ public struct NewProfileView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("New Profile")
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
.fileImporter(
|
||||
isPresented: $pickerPresented,
|
||||
allowedContentTypes: [.json],
|
||||
@@ -106,8 +109,7 @@ public struct NewProfileView: View {
|
||||
fileURL = urls[0]
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -118,26 +120,22 @@ public struct NewProfileView: View {
|
||||
isSaving = false
|
||||
}
|
||||
if profileName.isEmpty {
|
||||
errorMessage = "Missing profile name"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Missing profile name")
|
||||
return
|
||||
}
|
||||
if remotePath.isEmpty {
|
||||
if profileType == .icloud {
|
||||
errorMessage = "Missing path"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Missing path")
|
||||
return
|
||||
} else if profileType == .remote {
|
||||
errorMessage = "Missing URL"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Missing URL")
|
||||
return
|
||||
}
|
||||
}
|
||||
do {
|
||||
try createProfile0()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
await MainActor.run {
|
||||
@@ -173,13 +171,11 @@ public struct NewProfileView: View {
|
||||
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
|
||||
if fileImport {
|
||||
guard let fileURL else {
|
||||
errorMessage = "Missing file"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Missing file")
|
||||
return
|
||||
}
|
||||
if !fileURL.startAccessingSecurityScopedResource() {
|
||||
errorMessage = "Missing access to selected file"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Missing access to selected file")
|
||||
return
|
||||
}
|
||||
defer {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import Foundation
|
||||
import Libbox
|
||||
import Library
|
||||
import SwiftUI
|
||||
|
||||
public struct ProfileView: View {
|
||||
public static let notificationName = Notification.Name("\(FilePath.packageName).update-profile")
|
||||
|
||||
@Environment(\.importRemoteProfile) private var importRemoteProfile
|
||||
@State private var importRemoteProfileRequest: NewProfileView.ImportRequest?
|
||||
@State private var importRemoteProfilePresented = false
|
||||
|
||||
@State private var isLoading = true
|
||||
@State private var isUpdating = false
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
|
||||
@State private var alert: Alert?
|
||||
@State private var profileList: [Profile] = []
|
||||
|
||||
#if os(iOS)
|
||||
@@ -33,36 +36,51 @@ public struct ProfileView: View {
|
||||
}
|
||||
} else {
|
||||
#if os(iOS)
|
||||
FormView {
|
||||
NavigationLink {
|
||||
NewProfileView {
|
||||
Task.detached {
|
||||
doReload()
|
||||
ZStack {
|
||||
if let importRemoteProfileRequest {
|
||||
NavigationLink(
|
||||
destination: NewProfileView(importRemoteProfileRequest) {
|
||||
Task.detached {
|
||||
doReload()
|
||||
}
|
||||
},
|
||||
isActive: $importRemoteProfilePresented,
|
||||
label: {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
)
|
||||
}
|
||||
.disabled(editMode.isEditing)
|
||||
if profileList.isEmpty {
|
||||
Text("Empty Profiles")
|
||||
} else {
|
||||
List {
|
||||
ForEach(profileList, id: \.mustID) { profile in
|
||||
viewBuilder {
|
||||
if editMode.isEditing == true {
|
||||
Text(profile.name)
|
||||
} else {
|
||||
NavigationLink {
|
||||
EditProfileView().environmentObject(profile)
|
||||
} label: {
|
||||
FormView {
|
||||
NavigationLink {
|
||||
NewProfileView {
|
||||
Task.detached {
|
||||
doReload()
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("New Profile").foregroundColor(.accentColor)
|
||||
}
|
||||
.disabled(editMode.isEditing)
|
||||
if profileList.isEmpty {
|
||||
Text("Empty Profiles")
|
||||
} else {
|
||||
List {
|
||||
ForEach(profileList, id: \.mustID) { profile in
|
||||
viewBuilder {
|
||||
if editMode.isEditing == true {
|
||||
Text(profile.name)
|
||||
} else {
|
||||
NavigationLink {
|
||||
EditProfileView().environmentObject(profile)
|
||||
} label: {
|
||||
Text(profile.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onMove(perform: moveProfile)
|
||||
.onDelete(perform: deleteProfile)
|
||||
}
|
||||
.onMove(perform: moveProfile)
|
||||
.onDelete(perform: deleteProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +110,9 @@ public struct ProfileView: View {
|
||||
}, label: {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
})
|
||||
ShareLink(item: profile.shareLink) {
|
||||
Image(systemName: "square.and.arrow.up.fill")
|
||||
}
|
||||
}
|
||||
Button(action: {
|
||||
openWindow(id: EditProfileWindowView.windowID, value: profile.mustID)
|
||||
@@ -119,8 +140,13 @@ public struct ProfileView: View {
|
||||
}
|
||||
.disabled(isUpdating)
|
||||
.navigationTitle("Profiles")
|
||||
#if os(macOS)
|
||||
.onAppear {
|
||||
.alertBinding($alert, $isLoading)
|
||||
.onAppear {
|
||||
if let remoteProfile = importRemoteProfile.wrappedValue {
|
||||
importRemoteProfile.wrappedValue = nil
|
||||
createImportRemoteProfileDialog(remoteProfile)
|
||||
}
|
||||
#if os(macOS)
|
||||
if observer == nil {
|
||||
observer = NotificationCenter.default.addObserver(forName: ProfileView.notificationName, object: nil, queue: .main) { _ in
|
||||
Task.detached {
|
||||
@@ -128,40 +154,63 @@ public struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.onChange(of: importRemoteProfile.wrappedValue) { newValue in
|
||||
if let newValue {
|
||||
importRemoteProfile.wrappedValue = nil
|
||||
createImportRemoteProfileDialog(newValue)
|
||||
}
|
||||
.onDisappear {
|
||||
if let observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
observer = nil
|
||||
}
|
||||
#if os(macOS)
|
||||
.onDisappear {
|
||||
if let observer {
|
||||
NotificationCenter.default.removeObserver(observer)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: {
|
||||
openWindow(id: NewProfileView.windowID)
|
||||
}, label: {
|
||||
Label("New Profile", systemImage: "plus.square.fill")
|
||||
})
|
||||
}
|
||||
observer = nil
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button(action: {
|
||||
openWindow(id: NewProfileView.windowID)
|
||||
}, label: {
|
||||
Label("New Profile", systemImage: "plus.square.fill")
|
||||
})
|
||||
}
|
||||
}
|
||||
#elseif os(iOS)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
EditButton().disabled(profileList.isEmpty)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
EditButton().disabled(profileList.isEmpty)
|
||||
}
|
||||
.environment(\.editMode, $editMode)
|
||||
}
|
||||
.environment(\.editMode, $editMode)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func createImportRemoteProfileDialog(_ newValue: LibboxImportRemoteProfile) {
|
||||
importRemoteProfileRequest = .init(name: newValue.name, url: newValue.url)
|
||||
alert = Alert(
|
||||
title: Text("Import Remote Profile"),
|
||||
message: Text("Are you sure to import remote configuration \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
|
||||
primaryButton: .default(Text("Import")) {
|
||||
#if os(iOS)
|
||||
importRemoteProfilePresented = true
|
||||
#elseif os(macOS)
|
||||
openWindow(id: NewProfileView.windowID, value: importRemoteProfileRequest!)
|
||||
#endif
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
private func deleteSelectedProfiles(_ profileID: [Int64]) {
|
||||
do {
|
||||
if try ProfileManager.delete(by: profileID) > 0 {
|
||||
isLoading = true
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,8 +227,7 @@ public struct ProfileView: View {
|
||||
do {
|
||||
profileList = try ProfileManager.list()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -189,8 +237,7 @@ public struct ProfileView: View {
|
||||
do {
|
||||
_ = try profile.updateRemoteProfile()
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
isUpdating = false
|
||||
}
|
||||
@@ -200,8 +247,7 @@ public struct ProfileView: View {
|
||||
do {
|
||||
_ = try ProfileManager.delete(profile)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
isLoading = true
|
||||
@@ -216,8 +262,7 @@ public struct ProfileView: View {
|
||||
do {
|
||||
try ProfileManager.update(profileList)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -231,8 +276,7 @@ public struct ProfileView: View {
|
||||
do {
|
||||
_ = try ProfileManager.delete(profileToDelete)
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,7 @@ public struct SettingView: View {
|
||||
@State private var version = ""
|
||||
@State private var dataSize = ""
|
||||
@State private var taiwanFlagAvailable = false
|
||||
|
||||
@State private var errorPresented = false
|
||||
@State private var errorMessage = ""
|
||||
@State private var alert: Alert?
|
||||
|
||||
public init() {}
|
||||
|
||||
@@ -82,13 +80,11 @@ public struct SettingView: View {
|
||||
do {
|
||||
if let result = try await SystemExtension.install(forceUpdate: true) {
|
||||
if result == .willCompleteAfterReboot {
|
||||
errorMessage = "Need reboot"
|
||||
errorPresented = true
|
||||
alert = Alert(errorMessage: "Need reboot")
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,13 +130,7 @@ public struct SettingView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.alert(isPresented: $errorPresented) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("Ok"))
|
||||
)
|
||||
}
|
||||
.alertBinding($alert)
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
@@ -156,8 +146,7 @@ public struct SettingView: View {
|
||||
try SMAppService.mainApp.unregister()
|
||||
}
|
||||
} catch {
|
||||
errorMessage = error.localizedDescription
|
||||
errorPresented = true
|
||||
alert = Alert(error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user