Refactor Alerts
This commit is contained in:
@@ -1,47 +0,0 @@
|
|||||||
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!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct AlertState: Equatable {
|
||||||
|
public var title: String
|
||||||
|
public var message: String
|
||||||
|
public var primaryButton: ButtonState?
|
||||||
|
public var secondaryButton: ButtonState?
|
||||||
|
public var onDismiss: (() -> Void)?
|
||||||
|
|
||||||
|
public struct ButtonState: Equatable {
|
||||||
|
public var label: String
|
||||||
|
public var role: ButtonRole?
|
||||||
|
public var action: (() -> Void)?
|
||||||
|
|
||||||
|
public init(label: String, role: ButtonRole? = nil, action: (() -> Void)? = nil) {
|
||||||
|
self.label = label
|
||||||
|
self.role = role
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: ButtonState, rhs: ButtonState) -> Bool {
|
||||||
|
lhs.label == rhs.label && lhs.role == rhs.role
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func `default`(_ label: String, action: (() -> Void)? = nil) -> ButtonState {
|
||||||
|
ButtonState(label: label, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func cancel(_ label: String = String(localized: "Cancel"), action: (() -> Void)? = nil) -> ButtonState {
|
||||||
|
ButtonState(label: label, role: .cancel, action: action)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func destructive(_ label: String, action: (() -> Void)? = nil) -> ButtonState {
|
||||||
|
ButtonState(label: label, role: .destructive, action: action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(error: Error, dismiss: (() -> Void)? = nil) {
|
||||||
|
self.init(errorMessage: error.localizedDescription, dismiss: dismiss)
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(errorMessage: String, dismiss: (() -> Void)? = nil) {
|
||||||
|
title = String(localized: "Error")
|
||||||
|
message = errorMessage
|
||||||
|
primaryButton = .default(String(localized: "Ok"), action: dismiss)
|
||||||
|
secondaryButton = nil
|
||||||
|
onDismiss = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(title: String, message: String, dismissButton: ButtonState? = nil) {
|
||||||
|
self.title = title
|
||||||
|
self.message = message
|
||||||
|
primaryButton = dismissButton ?? .default(String(localized: "Ok"))
|
||||||
|
secondaryButton = nil
|
||||||
|
onDismiss = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(title: String, message: String, primaryButton: ButtonState, secondaryButton: ButtonState) {
|
||||||
|
self.title = title
|
||||||
|
self.message = message
|
||||||
|
self.primaryButton = primaryButton
|
||||||
|
self.secondaryButton = secondaryButton
|
||||||
|
onDismiss = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(title: String, message: String, primaryButton: ButtonState, secondaryButton: ButtonState, onDismiss: @escaping () -> Void) {
|
||||||
|
self.title = title
|
||||||
|
self.message = message
|
||||||
|
self.primaryButton = primaryButton
|
||||||
|
self.secondaryButton = secondaryButton
|
||||||
|
self.onDismiss = onDismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: AlertState, rhs: AlertState) -> Bool {
|
||||||
|
lhs.title == rhs.title && lhs.message == rhs.message &&
|
||||||
|
lhs.primaryButton == rhs.primaryButton && lhs.secondaryButton == rhs.secondaryButton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension View {
|
||||||
|
@ViewBuilder
|
||||||
|
func alert(_ binding: Binding<AlertState?>) -> some View {
|
||||||
|
alert(
|
||||||
|
binding.wrappedValue?.title ?? "",
|
||||||
|
isPresented: Binding(
|
||||||
|
get: { binding.wrappedValue != nil },
|
||||||
|
set: { newValue, _ in
|
||||||
|
if !newValue {
|
||||||
|
binding.wrappedValue?.onDismiss?()
|
||||||
|
binding.wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
presenting: binding.wrappedValue
|
||||||
|
) { alertState in
|
||||||
|
if let secondary = alertState.secondaryButton {
|
||||||
|
Button(role: alertState.primaryButton?.role) {
|
||||||
|
alertState.primaryButton?.action?()
|
||||||
|
} label: {
|
||||||
|
Text(alertState.primaryButton?.label ?? "Ok")
|
||||||
|
}
|
||||||
|
Button(role: secondary.role) {
|
||||||
|
secondary.action?()
|
||||||
|
} label: {
|
||||||
|
Text(secondary.label)
|
||||||
|
}
|
||||||
|
} else if let primary = alertState.primaryButton {
|
||||||
|
Button(role: primary.role) {
|
||||||
|
primary.action?()
|
||||||
|
} label: {
|
||||||
|
Text(primary.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} message: { alertState in
|
||||||
|
Text(alertState.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
func alert(_ binding: Binding<AlertState?>, isLoading: Binding<Bool>) -> some View {
|
||||||
|
alert(
|
||||||
|
binding.wrappedValue?.title ?? "",
|
||||||
|
isPresented: Binding(
|
||||||
|
get: { binding.wrappedValue != nil },
|
||||||
|
set: { newValue, _ in
|
||||||
|
if !newValue, !isLoading.wrappedValue {
|
||||||
|
binding.wrappedValue?.onDismiss?()
|
||||||
|
binding.wrappedValue = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
presenting: binding.wrappedValue
|
||||||
|
) { alertState in
|
||||||
|
if let secondary = alertState.secondaryButton {
|
||||||
|
Button(role: alertState.primaryButton?.role) {
|
||||||
|
alertState.primaryButton?.action?()
|
||||||
|
} label: {
|
||||||
|
Text(alertState.primaryButton?.label ?? "Ok")
|
||||||
|
}
|
||||||
|
Button(role: secondary.role) {
|
||||||
|
secondary.action?()
|
||||||
|
} label: {
|
||||||
|
Text(secondary.label)
|
||||||
|
}
|
||||||
|
} else if let primary = alertState.primaryButton {
|
||||||
|
Button(role: primary.role) {
|
||||||
|
primary.action?()
|
||||||
|
} label: {
|
||||||
|
Text(primary.label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} message: { alertState in
|
||||||
|
Text(alertState.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,20 +2,20 @@ import SwiftUI
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
open class BaseViewModel: ObservableObject {
|
open class BaseViewModel: ObservableObject {
|
||||||
@Published public var alert: Alert?
|
@Published public var alert: AlertState?
|
||||||
@Published public var isLoading = false
|
@Published public var isLoading = false
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
public func showError(_ error: Error) {
|
public func showError(_ error: Error) {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func execute(_ operation: () async throws -> Void) async {
|
public func execute(_ operation: () async throws -> Void) async {
|
||||||
do {
|
do {
|
||||||
try await operation()
|
try await operation()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ open class BaseViewModel: ObservableObject {
|
|||||||
try await operation()
|
try await operation()
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import SwiftUI
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct ProfileShareButton<Label>: View where Label: View {
|
public struct ProfileShareButton<Label>: View where Label: View {
|
||||||
private let alert: Binding<Alert?>
|
private let alert: Binding<AlertState?>
|
||||||
private let profile: Profile
|
private let profile: Profile
|
||||||
private let label: () -> Label
|
private let label: () -> Label
|
||||||
|
|
||||||
public init(_ alert: Binding<Alert?>, _ profile: Profile, label: @escaping () -> Label) {
|
public init(_ alert: Binding<AlertState?>, _ profile: Profile, label: @escaping () -> Label) {
|
||||||
self.alert = alert
|
self.alert = alert
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
self.label = label
|
self.label = label
|
||||||
@@ -44,13 +44,13 @@ public struct ShareButtonCompat<Label>: View where Label: View {
|
|||||||
private let label: () -> Label
|
private let label: () -> Label
|
||||||
private let itemURL: () throws -> URL
|
private let itemURL: () throws -> URL
|
||||||
|
|
||||||
@Binding private var alert: Alert?
|
@Binding private var alert: AlertState?
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@State private var sharePresented = false
|
@State private var sharePresented = false
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public init(_ alert: Binding<Alert?>, @ViewBuilder label: @escaping () -> Label, itemURL: @escaping () throws -> URL) {
|
public init(_ alert: Binding<AlertState?>, @ViewBuilder label: @escaping () -> Label, itemURL: @escaping () throws -> URL) {
|
||||||
_alert = alert
|
_alert = alert
|
||||||
self.label = label
|
self.label = label
|
||||||
self.itemURL = itemURL
|
self.itemURL = itemURL
|
||||||
@@ -83,7 +83,7 @@ public struct ShareButtonCompat<Label>: View where Label: View {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,10 +109,10 @@ public struct ShareButtonCompat<Label>: View where Label: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
private struct SharingServicePicker: NSViewRepresentable {
|
private struct SharingServicePicker: NSViewRepresentable {
|
||||||
@Binding private var isPresented: Bool
|
@Binding private var isPresented: Bool
|
||||||
@Binding private var alert: Alert?
|
@Binding private var alert: AlertState?
|
||||||
private let item: () throws -> URL
|
private let item: () throws -> URL
|
||||||
|
|
||||||
init(_ isPresented: Binding<Bool>, _ alert: Binding<Alert?>, _ item: @escaping () throws -> URL) {
|
init(_ isPresented: Binding<Bool>, _ alert: Binding<AlertState?>, _ item: @escaping () throws -> URL) {
|
||||||
_isPresented = isPresented
|
_isPresented = isPresented
|
||||||
_alert = alert
|
_alert = alert
|
||||||
self.item = item
|
self.item = item
|
||||||
@@ -132,7 +132,7 @@ public struct ShareButtonCompat<Label>: View where Label: View {
|
|||||||
picker.show(relativeTo: .zero, of: nsView, preferredEdge: .minY)
|
picker.show(relativeTo: .zero, of: nsView, preferredEdge: .minY)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public struct ConnectionListView: View {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.searchable(text: $viewModel.searchText)
|
.searchable(text: $viewModel.searchText)
|
||||||
#endif
|
#endif
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.connect()
|
viewModel.connect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public class ConnectionListViewModel: BaseViewModel {
|
|||||||
private var saveStateFilterTask: Task<Void, Never>?
|
private var saveStateFilterTask: Task<Void, Never>?
|
||||||
private var saveSortTask: Task<Void, Never>?
|
private var saveSortTask: Task<Void, Never>?
|
||||||
|
|
||||||
public override init() {
|
override public init() {
|
||||||
connectionStateFilter = .active
|
connectionStateFilter = .active
|
||||||
connectionSort = .byDate
|
connectionSort = .byDate
|
||||||
super.init()
|
super.init()
|
||||||
@@ -70,7 +70,7 @@ public class ConnectionListViewModel: BaseViewModel {
|
|||||||
do {
|
do {
|
||||||
try LibboxNewStandaloneCommandClient()!.closeConnections()
|
try LibboxNewStandaloneCommandClient()!.closeConnections()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public struct ConnectionView: View {
|
|||||||
LibboxFormatDuration(Int64((closedAt.timeIntervalSince1970 - createdAt.timeIntervalSince1970) * 1000))
|
LibboxFormatDuration(Int64((closedAt.timeIntervalSince1970 - createdAt.timeIntervalSince1970) * 1000))
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
FormNavigationLink {
|
FormNavigationLink {
|
||||||
@@ -86,7 +86,7 @@ public struct ConnectionView: View {
|
|||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
.buttonStyle(.borderless)
|
.buttonStyle(.borderless)
|
||||||
#endif
|
#endif
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
if connection.closedAt == nil {
|
if connection.closedAt == nil {
|
||||||
Button("Close", role: .destructive) {
|
Button("Close", role: .destructive) {
|
||||||
@@ -114,7 +114,7 @@ public struct ConnectionView: View {
|
|||||||
try await LibboxNewStandaloneCommandClient()!.closeConnection(connection.id)
|
try await LibboxNewStandaloneCommandClient()!.closeConnection(connection.id)
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import SwiftUI
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
content.onAppear {
|
content.onAppear {
|
||||||
guard !ApplicationLibrary.inPreview else {
|
guard !ApplicationLibrary.inPreview, profile.status.isConnected else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
Task {
|
Task {
|
||||||
@@ -117,7 +117,7 @@ import SwiftUI
|
|||||||
updateButtonVisibility()
|
updateButtonVisibility()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
.alertBinding($coordinator.alert)
|
.alert($coordinator.alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder private var overviewPage: some View {
|
@ViewBuilder private var overviewPage: some View {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import SwiftUI
|
|||||||
public struct ClashModeCard: View {
|
public struct ClashModeCard: View {
|
||||||
@EnvironmentObject private var commandClient: CommandClient
|
@EnvironmentObject private var commandClient: CommandClient
|
||||||
@State private var clashMode: String = ""
|
@State private var clashMode: String = ""
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ public struct ClashModeCard: View {
|
|||||||
.onChangeCompat(of: commandClient.clashMode) { newValue in
|
.onChangeCompat(of: commandClient.clashMode) { newValue in
|
||||||
clashMode = newValue
|
clashMode = newValue
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ public struct ClashModeCard: View {
|
|||||||
try LibboxNewStandaloneCommandClient()!.setClashMode(newMode)
|
try LibboxNewStandaloneCommandClient()!.setClashMode(newMode)
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public struct ProfileCard: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var headerView: some View {
|
private var headerView: some View {
|
||||||
@@ -254,7 +254,7 @@ extension ProfileCard {
|
|||||||
@Published var showManageProfiles = false
|
@Published var showManageProfiles = false
|
||||||
@Published var showQRCode = false
|
@Published var showQRCode = false
|
||||||
@Published var isUpdating = false
|
@Published var isUpdating = false
|
||||||
@Published var alert: Alert?
|
@Published var alert: AlertState?
|
||||||
@Published var profileToEdit: Profile?
|
@Published var profileToEdit: Profile?
|
||||||
|
|
||||||
func updateProfile(_ profile: Profile, environments: ExtensionEnvironments) async {
|
func updateProfile(_ profile: Profile, environments: ExtensionEnvironments) async {
|
||||||
@@ -264,9 +264,9 @@ extension ProfileCard {
|
|||||||
try await profile.updateRemoteProfile()
|
try await profile.updateRemoteProfile()
|
||||||
environments.profileUpdate.send()
|
environments.profileUpdate.send()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Update Failed"),
|
title: String(localized: "Update Failed"),
|
||||||
message: Text(error.localizedDescription)
|
message: error.localizedDescription
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ extension ProfileCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(viewModel.isUpdating)
|
.disabled(viewModel.isUpdating)
|
||||||
.alertBinding($viewModel.alert, $viewModel.isLoading)
|
.alert($viewModel.alert, isLoading: $viewModel.isLoading)
|
||||||
.onReceive(environments.profileUpdate) { _ in
|
.onReceive(environments.profileUpdate) { _ in
|
||||||
Task {
|
Task {
|
||||||
await viewModel.doReload()
|
await viewModel.doReload()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public struct ExtensionStatusView: View {
|
|||||||
@EnvironmentObject private var commandClient: CommandClient
|
@EnvironmentObject private var commandClient: CommandClient
|
||||||
|
|
||||||
@State private var columnCount: Int = 4
|
@State private var columnCount: Int = 4
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
private let infoFont = Font.system(.caption, design: .monospaced)
|
private let infoFont = Font.system(.caption, design: .monospaced)
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ public struct ExtensionStatusView: View {
|
|||||||
.frame(alignment: .topLeading)
|
.frame(alignment: .topLeading)
|
||||||
.padding([.top, .leading, .trailing])
|
.padding([.top, .leading, .trailing])
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateColumnCount(_ width: Double) {
|
private func updateColumnCount(_ width: Double) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import SwiftUI
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct InstallProfileButton: View {
|
public struct InstallProfileButton: View {
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
private let callback: () async -> Void
|
private let callback: () async -> Void
|
||||||
public init(_ callback: @escaping (() async -> Void)) {
|
public init(_ callback: @escaping (() async -> Void)) {
|
||||||
@@ -18,7 +18,7 @@ public struct InstallProfileButton: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Label("Install Network Extension", systemImage: "lock.doc.fill")
|
Label("Install Network Extension", systemImage: "lock.doc.fill")
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func installProfile() async {
|
private func installProfile() async {
|
||||||
@@ -26,7 +26,7 @@ public struct InstallProfileButton: View {
|
|||||||
try await ExtensionProfile.install()
|
try await ExtensionProfile.install()
|
||||||
await callback()
|
await callback()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
public struct InstallSystemExtensionButton: View {
|
public struct InstallSystemExtensionButton: View {
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
private let callback: () async -> Void
|
private let callback: () async -> Void
|
||||||
public init(_ callback: @escaping () async -> Void) {
|
public init(_ callback: @escaping () async -> Void) {
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
@@ -19,19 +19,19 @@
|
|||||||
} label: {
|
} label: {
|
||||||
Label("Install System Extension", systemImage: "lock.doc.fill")
|
Label("Install System Extension", systemImage: "lock.doc.fill")
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func installSystemExtension() async {
|
private func installSystemExtension() async {
|
||||||
do {
|
do {
|
||||||
if let result = try await SystemExtension.install() {
|
if let result = try await SystemExtension.install() {
|
||||||
if result == .willCompleteAfterReboot {
|
if result == .willCompleteAfterReboot {
|
||||||
alert = Alert(errorMessage: String(localized: "Need Reboot"))
|
alert = AlertState(errorMessage: String(localized: "Need Reboot"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await callback()
|
await callback()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public struct StartStopButton: View {
|
|||||||
private struct ToggleConnectionButton: View {
|
private struct ToggleConnectionButton: View {
|
||||||
@EnvironmentObject private var environments: ExtensionEnvironments
|
@EnvironmentObject private var environments: ExtensionEnvironments
|
||||||
@EnvironmentObject private var profile: ExtensionProfile
|
@EnvironmentObject private var profile: ExtensionProfile
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
@State private var currentTime = Date()
|
@State private var currentTime = Date()
|
||||||
|
|
||||||
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||||
@@ -105,7 +105,7 @@ public struct StartStopButton: View {
|
|||||||
.modifier(PrimaryTintModifier())
|
.modifier(PrimaryTintModifier())
|
||||||
#endif
|
#endif
|
||||||
.disabled(!profile.status.isEnabled)
|
.disabled(!profile.status.isEnabled)
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
.onReceive(timer) { _ in
|
.onReceive(timer) { _ in
|
||||||
currentTime = Date()
|
currentTime = Date()
|
||||||
}
|
}
|
||||||
@@ -146,7 +146,7 @@ public struct StartStopButton: View {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ public struct DashboardView: View {
|
|||||||
private func handleImportProfile() {
|
private func handleImportProfile() {
|
||||||
if let profile = importProfile.wrappedValue {
|
if let profile = importProfile.wrappedValue {
|
||||||
importProfile.wrappedValue = nil
|
importProfile.wrappedValue = nil
|
||||||
coordinator.alert = Alert(
|
coordinator.alert = AlertState(
|
||||||
title: Text("Import Profile"),
|
title: String(localized: "Import Profile"),
|
||||||
message: Text("Are you sure to import profile \(profile.name)?"),
|
message: String(localized: "Are you sure to import profile \(profile.name)?"),
|
||||||
primaryButton: .default(Text("Import")) {
|
primaryButton: .default(String(localized: "Import")) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await profile.importProfile()
|
try await profile.importProfile()
|
||||||
} catch {
|
} catch {
|
||||||
coordinator.alert = Alert(error)
|
coordinator.alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
environments.profileUpdate.send()
|
environments.profileUpdate.send()
|
||||||
@@ -70,10 +70,10 @@ public struct DashboardView: View {
|
|||||||
private func handleImportRemoteProfile() {
|
private func handleImportRemoteProfile() {
|
||||||
if let remoteProfile = importRemoteProfile.wrappedValue {
|
if let remoteProfile = importRemoteProfile.wrappedValue {
|
||||||
importRemoteProfile.wrappedValue = nil
|
importRemoteProfile.wrappedValue = nil
|
||||||
coordinator.alert = Alert(
|
coordinator.alert = AlertState(
|
||||||
title: Text("Import Remote Profile"),
|
title: String(localized: "Import Remote Profile"),
|
||||||
message: Text("Are you sure to import remote profile \(remoteProfile.name)? You will connect to \(remoteProfile.host) to download the configuration."),
|
message: String(localized: "Are you sure to import remote profile \(remoteProfile.name)? You will connect to \(remoteProfile.host) to download the configuration."),
|
||||||
primaryButton: .default(Text("Import")) {
|
primaryButton: .default(String(localized: "Import")) {
|
||||||
importRemoteProfileRequest = .init(name: remoteProfile.name, url: remoteProfile.url)
|
importRemoteProfileRequest = .init(name: remoteProfile.name, url: remoteProfile.url)
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel()
|
secondaryButton: .cancel()
|
||||||
@@ -117,7 +117,7 @@ public struct DashboardView: View {
|
|||||||
} else if let profile = environments.extensionProfile {
|
} else if let profile = environments.extensionProfile {
|
||||||
activeDashboardView
|
activeDashboardView
|
||||||
.environmentObject(profile)
|
.environmentObject(profile)
|
||||||
.alertBinding($coordinator.alert)
|
.alert($coordinator.alert)
|
||||||
.onChangeCompat(of: profile.status) { status in
|
.onChangeCompat(of: profile.status) { status in
|
||||||
coordinator.handleStatusChange(status, profile: profile)
|
coordinator.handleStatusChange(status, profile: profile)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
public var onEmptyProfilesChange: ((Bool) -> Void)?
|
public var onEmptyProfilesChange: ((Bool) -> Void)?
|
||||||
private var openURL: ((URL) -> Void)?
|
private var openURL: ((URL) -> Void)?
|
||||||
|
|
||||||
public override init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
isLoading = true
|
isLoading = true
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
await SharedPreferences.selectedProfileID.set(selectedProfileID)
|
await SharedPreferences.selectedProfileID.set(selectedProfileID)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
systemProxyAvailable = status.available
|
systemProxyAvailable = status.available
|
||||||
systemProxyEnabled = status.enabled
|
systemProxyEnabled = status.enabled
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,34 +123,29 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
guard reports.hasNext() else { return }
|
guard reports.hasNext() else { return }
|
||||||
|
|
||||||
let report = reports.next()!
|
let report = reports.next()!
|
||||||
|
let continueChain: () -> Void = { [weak self] in
|
||||||
|
_ = Task.detached {
|
||||||
|
try? await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC)
|
||||||
|
await self?.loopShowDeprecateNotes(reports)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if report.migrationLink.isEmpty {
|
if report.migrationLink.isEmpty {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Deprecated Warning"),
|
title: String(localized: "Deprecated Warning"),
|
||||||
message: Text(report.message()),
|
message: report.message(),
|
||||||
dismissButton: .cancel(Text("Ok")) {
|
dismissButton: .cancel(String(localized: "Ok"))
|
||||||
Task.detached { [weak self] in
|
|
||||||
try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC)
|
|
||||||
await self?.loopShowDeprecateNotes(reports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
alert?.onDismiss = continueChain
|
||||||
} else {
|
} else {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Deprecated Warning"),
|
title: String(localized: "Deprecated Warning"),
|
||||||
message: Text(report.message()),
|
message: report.message(),
|
||||||
primaryButton: .default(Text("Documentation")) {
|
primaryButton: .default(String(localized: "Documentation")) {
|
||||||
self.openURL?(URL(string: report.migrationLink)!)
|
self.openURL?(URL(string: report.migrationLink)!)
|
||||||
Task.detached { [weak self] in
|
|
||||||
try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC)
|
|
||||||
await self?.loopShowDeprecateNotes(reports)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel(Text("Ok")) {
|
secondaryButton: .cancel(String(localized: "Ok")),
|
||||||
Task.detached { [weak self] in
|
onDismiss: continueChain
|
||||||
try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC)
|
|
||||||
await self?.loopShowDeprecateNotes(reports)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,10 +160,10 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
if myError.domain == "Library.FullDiskAccessPermissionRequired" {
|
if myError.domain == "Library.FullDiskAccessPermissionRequired" {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Full Disk Access permission is required"),
|
title: String(localized: "Full Disk Access permission is required"),
|
||||||
message: Text("Please grant the permission for **SFMExtension**, then we can continue."),
|
message: String(localized: "Please grant the permission for **SFMExtension**, then we can continue."),
|
||||||
primaryButton: .default(Text("Authorize"), action: openFDASettings),
|
primaryButton: .default(String(localized: "Authorize"), action: openFDASettings),
|
||||||
secondaryButton: .cancel()
|
secondaryButton: .cancel()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -176,7 +171,7 @@ public final class DashboardViewModel: BaseViewModel {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(title: Text("Service Error"), message: Text(myError.localizedDescription))
|
alert = AlertState(title: String(localized: "Service Error"), message: myError.localizedDescription)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public struct OverviewView: View {
|
|||||||
.onChangeCompat(of: cardConfigurationVersion) { _ in
|
.onChangeCompat(of: cardConfigurationVersion) { _ in
|
||||||
Task { await configuration.reload() }
|
Task { await configuration.reload() }
|
||||||
}
|
}
|
||||||
.alertBinding($coordinator.alert)
|
.alert($coordinator.alert)
|
||||||
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || coordinator.reasserting))
|
.disabled(!ApplicationLibrary.inPreview && (!profile.status.isSwitchable || coordinator.reasserting))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public final class OverviewViewModel: BaseViewModel {
|
|||||||
do {
|
do {
|
||||||
try await serviceReload()
|
try await serviceReload()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reasserting = false
|
reasserting = false
|
||||||
@@ -46,7 +46,7 @@ public final class OverviewViewModel: BaseViewModel {
|
|||||||
await MainActor.run { reasserting = false }
|
await MainActor.run { reasserting = false }
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run { alert = Alert(error) }
|
await MainActor.run { alert = AlertState(error: error) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public struct GroupListView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environmentObject(viewModel)
|
.environmentObject(viewModel)
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.connect()
|
viewModel.connect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public class GroupListViewModel: BaseViewModel {
|
|||||||
|
|
||||||
private var pendingSelections: [String: String] = [:]
|
private var pendingSelections: [String: String] = [:]
|
||||||
|
|
||||||
public override init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
isLoading = true
|
isLoading = true
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ public class GroupListViewModel: BaseViewModel {
|
|||||||
try await LibboxNewStandaloneCommandClient()!.selectOutbound(groupTag, outboundTag: outboundTag)
|
try await LibboxNewStandaloneCommandClient()!.selectOutbound(groupTag, outboundTag: outboundTag)
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ public class GroupListViewModel: BaseViewModel {
|
|||||||
try await LibboxNewStandaloneCommandClient()!.setGroupExpand(tag, isExpand: isExpand)
|
try await LibboxNewStandaloneCommandClient()!.setGroupExpand(tag, isExpand: isExpand)
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ public class GroupListViewModel: BaseViewModel {
|
|||||||
try await LibboxNewStandaloneCommandClient()!.urlTest(tag)
|
try await LibboxNewStandaloneCommandClient()!.urlTest(tag)
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ private struct LogViewContent: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.background(
|
.background(
|
||||||
LogExportView(
|
LogExportView(
|
||||||
showFileExporter: $viewModel.showFileExporter,
|
showFileExporter: $viewModel.showFileExporter,
|
||||||
@@ -242,7 +242,7 @@ private struct LogViewContent: View {
|
|||||||
private struct LogExportView: View {
|
private struct LogExportView: View {
|
||||||
@Binding var showFileExporter: Bool
|
@Binding var showFileExporter: Bool
|
||||||
@Binding var logFileURL: URL?
|
@Binding var logFileURL: URL?
|
||||||
@Binding var alert: Alert?
|
@Binding var alert: AlertState?
|
||||||
@State private var showShareSheet = false
|
@State private var showShareSheet = false
|
||||||
let cleanup: () -> Void
|
let cleanup: () -> Void
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ private struct LogViewContent: View {
|
|||||||
cleanup()
|
cleanup()
|
||||||
logFileURL = nil
|
logFileURL = nil
|
||||||
if case let .failure(error) = result {
|
if case let .failure(error) = result {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $showShareSheet) {
|
.sheet(isPresented: $showShareSheet) {
|
||||||
@@ -320,7 +320,7 @@ private struct LogViewContent: View {
|
|||||||
#elseif os(macOS)
|
#elseif os(macOS)
|
||||||
private struct ShareView: NSViewRepresentable {
|
private struct ShareView: NSViewRepresentable {
|
||||||
let items: [Any]
|
let items: [Any]
|
||||||
@Binding var alert: Alert?
|
@Binding var alert: AlertState?
|
||||||
|
|
||||||
func makeNSView(context _: Context) -> NSView {
|
func makeNSView(context _: Context) -> NSView {
|
||||||
let view = NSView()
|
let view = NSView()
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class LogViewModel: ObservableObject {
|
|||||||
@Published public var searchText = ""
|
@Published public var searchText = ""
|
||||||
@Published public var isSearching = false
|
@Published public var isSearching = false
|
||||||
@Published public var filteredLogs: [LogEntry] = []
|
@Published public var filteredLogs: [LogEntry] = []
|
||||||
@Published public var alert: Alert?
|
@Published public var alert: AlertState?
|
||||||
@Published public var showFileExporter = false
|
@Published public var showFileExporter = false
|
||||||
@Published public var logFileURL: URL?
|
@Published public var logFileURL: URL?
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ public class LogViewModel: ObservableObject {
|
|||||||
try text.write(to: fileURL, atomically: true, encoding: .utf8)
|
try text.write(to: fileURL, atomically: true, encoding: .utf8)
|
||||||
logFileURL = fileURL
|
logFileURL = fileURL
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.navigationTitle(navigationTitle)
|
.navigationTitle(navigationTitle)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
do {
|
do {
|
||||||
try await loadContentBackground()
|
try await loadContentBackground()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
do {
|
do {
|
||||||
try await saveContentBackground(profile)
|
try await saveContentBackground(profile)
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isChanged = false
|
isChanged = false
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ public struct EditProfileView: View {
|
|||||||
viewModel.markAsChanged()
|
viewModel.markAsChanged()
|
||||||
}
|
}
|
||||||
.disabled(viewModel.isLoading)
|
.disabled(viewModel.isLoading)
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
private var iOSBody: some View {
|
private var iOSBody: some View {
|
||||||
@@ -122,7 +122,7 @@ public struct EditProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.navigationTitle("Edit Profile")
|
.navigationTitle("Edit Profile")
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public final class EditProfileViewModel: BaseViewModel {
|
|||||||
try await profile.updateRemoteProfile()
|
try await profile.updateRemoteProfile()
|
||||||
environments.profileUpdate.send()
|
environments.profileUpdate.send()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ public final class EditProfileViewModel: BaseViewModel {
|
|||||||
do {
|
do {
|
||||||
try await ProfileManager.delete(profile)
|
try await ProfileManager.delete(profile)
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
environments.profileUpdate.send()
|
environments.profileUpdate.send()
|
||||||
@@ -46,7 +46,7 @@ public final class EditProfileViewModel: BaseViewModel {
|
|||||||
#endif
|
#endif
|
||||||
try await profile.onProfileUpdated()
|
try await profile.onProfileUpdated()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isChanged = false
|
isChanged = false
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.focusSection()
|
.focusSection()
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.navigationTitle("Import Profile")
|
.navigationTitle("Import Profile")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
case let .failed(error):
|
case let .failed(error):
|
||||||
DispatchQueue.main.async { [self] in
|
DispatchQueue.main.async { [self] in
|
||||||
reset()
|
reset()
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
do {
|
do {
|
||||||
try await loopMessages(environments: environments, dismiss: dismiss)
|
try await loopMessages(environments: environments, dismiss: dismiss)
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
try socket.write(request.encode())
|
try socket.write(request.encode())
|
||||||
isImporting = true
|
isImporting = true
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ public struct NewProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(viewModel.isSaving)
|
.disabled(viewModel.isSaving)
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.fileImporter(
|
.fileImporter(
|
||||||
isPresented: $viewModel.pickerPresented,
|
isPresented: $viewModel.pickerPresented,
|
||||||
allowedContentTypes: [.json],
|
allowedContentTypes: [.json],
|
||||||
@@ -170,7 +170,7 @@ public struct NewProfileView: View {
|
|||||||
viewModel.fileURL = urls[0]
|
viewModel.fileURL = urls[0]
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
viewModel.alert = Alert(error)
|
viewModel.alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ public struct NewProfileView: View {
|
|||||||
formContent
|
formContent
|
||||||
.navigationTitle("New Profile")
|
.navigationTitle("New Profile")
|
||||||
.disabled(viewModel.isSaving)
|
.disabled(viewModel.isSaving)
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.fileImporter(
|
.fileImporter(
|
||||||
isPresented: $viewModel.pickerPresented,
|
isPresented: $viewModel.pickerPresented,
|
||||||
@@ -193,7 +193,7 @@ public struct NewProfileView: View {
|
|||||||
viewModel.fileURL = urls[0]
|
viewModel.fileURL = urls[0]
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
viewModel.alert = Alert(error)
|
viewModel.alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,17 +45,17 @@ public final class NewProfileViewModel: BaseViewModel {
|
|||||||
defer { isSaving = false }
|
defer { isSaving = false }
|
||||||
|
|
||||||
guard !profileName.isEmpty else {
|
guard !profileName.isEmpty else {
|
||||||
alert = Alert(errorMessage: String(localized: "Missing profile name"))
|
alert = AlertState(errorMessage: String(localized: "Missing profile name"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if profileType == .icloud, remotePath.isEmpty {
|
if profileType == .icloud, remotePath.isEmpty {
|
||||||
alert = Alert(errorMessage: String(localized: "Missing path"))
|
alert = AlertState(errorMessage: String(localized: "Missing path"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if profileType == .remote, remotePath.isEmpty {
|
if profileType == .remote, remotePath.isEmpty {
|
||||||
alert = Alert(errorMessage: String(localized: "Missing URL"))
|
alert = AlertState(errorMessage: String(localized: "Missing URL"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public final class NewProfileViewModel: BaseViewModel {
|
|||||||
do {
|
do {
|
||||||
createdProfile = try await createProfileBackground()
|
createdProfile = try await createProfileBackground()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ public struct ProfileView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.disabled(viewModel.isUpdating)
|
.disabled(viewModel.isUpdating)
|
||||||
.alertBinding($viewModel.alert, $viewModel.isLoading)
|
.alert($viewModel.alert, isLoading: $viewModel.isLoading)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if let profile = importProfile.wrappedValue {
|
if let profile = importProfile.wrappedValue {
|
||||||
importProfile.wrappedValue = nil
|
importProfile.wrappedValue = nil
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
|
|
||||||
private weak var environments: ExtensionEnvironments?
|
private weak var environments: ExtensionEnvironments?
|
||||||
|
|
||||||
public override init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
isLoading = true
|
isLoading = true
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,15 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func createImportProfileDialog(_ profile: LibboxProfileContent) {
|
public func createImportProfileDialog(_ profile: LibboxProfileContent) {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Import Profile"),
|
title: String(localized: "Import Profile"),
|
||||||
message: Text("Are you sure to import profile \(profile.name)?"),
|
message: String(localized: "Are you sure to import profile \(profile.name)?"),
|
||||||
primaryButton: .default(Text("Import")) {
|
primaryButton: .default(String(localized: "Import")) {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await profile.importProfile()
|
try await profile.importProfile()
|
||||||
} catch {
|
} catch {
|
||||||
self.alert = Alert(error)
|
self.alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await self.doReload()
|
await self.doReload()
|
||||||
@@ -47,10 +47,10 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
|
|
||||||
public func createImportRemoteProfileDialog(_ newValue: LibboxImportRemoteProfile) {
|
public func createImportRemoteProfileDialog(_ newValue: LibboxImportRemoteProfile) {
|
||||||
importRemoteProfileRequest = .init(name: newValue.name, url: newValue.url)
|
importRemoteProfileRequest = .init(name: newValue.name, url: newValue.url)
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Import Remote Profile"),
|
title: String(localized: "Import Remote Profile"),
|
||||||
message: Text("Are you sure to import remote profile \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
|
message: String(localized: "Are you sure to import remote profile \(newValue.name)? You will connect to \(newValue.host) to download the configuration."),
|
||||||
primaryButton: .default(Text("Import")) {
|
primaryButton: .default(String(localized: "Import")) {
|
||||||
self.importRemoteProfilePresented = true
|
self.importRemoteProfilePresented = true
|
||||||
},
|
},
|
||||||
secondaryButton: .cancel()
|
secondaryButton: .cancel()
|
||||||
@@ -70,7 +70,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
do {
|
do {
|
||||||
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
|
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +87,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
_ = try await profile.updateRemoteProfile()
|
_ = try await profile.updateRemoteProfile()
|
||||||
} catch {
|
} catch {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
environments?.profileUpdate.send()
|
environments?.profileUpdate.send()
|
||||||
environments?.emptyProfiles = profileList.isEmpty
|
environments?.emptyProfiles = profileList.isEmpty
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
try await ProfileManager.update(profileList.map(\.origin))
|
try await ProfileManager.update(profileList.map(\.origin))
|
||||||
environments?.profileUpdate.send()
|
environments?.profileUpdate.send()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,7 +129,7 @@ public class ProfileViewModel: BaseViewModel {
|
|||||||
environments?.emptyProfiles = profileList.isEmpty
|
environments?.emptyProfiles = profileList.isEmpty
|
||||||
environments?.profileUpdate.send()
|
environments?.profileUpdate.send()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public struct AppView: View {
|
|||||||
@Environment(\.showMenuBarExtra) private var showMenuBarExtra
|
@Environment(\.showMenuBarExtra) private var showMenuBarExtra
|
||||||
@State private var menuBarExtraInBackground = false
|
@State private var menuBarExtraInBackground = false
|
||||||
|
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
@@ -72,7 +72,7 @@ public struct AppView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
.navigationTitle("App")
|
.navigationTitle("App")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
@@ -101,7 +101,7 @@ public struct AppView: View {
|
|||||||
try SMAppService.mainApp.unregister()
|
try SMAppService.mainApp.unregister()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +110,19 @@ public struct AppView: View {
|
|||||||
if let result = try await SystemExtension.install(forceUpdate: true) {
|
if let result = try await SystemExtension.install(forceUpdate: true) {
|
||||||
switch result {
|
switch result {
|
||||||
case .completed:
|
case .completed:
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Update"),
|
title: String(localized: "Update"),
|
||||||
message: Text("System Extension updated."),
|
message: String(localized: "System Extension updated.")
|
||||||
dismissButton: .default(Text("Ok")) {}
|
|
||||||
)
|
)
|
||||||
case .willCompleteAfterReboot:
|
case .willCompleteAfterReboot:
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Update"),
|
title: String(localized: "Update"),
|
||||||
message: Text("Reboot required."),
|
message: String(localized: "Reboot required.")
|
||||||
dismissButton: .default(Text("Ok")) {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,21 +131,19 @@ public struct AppView: View {
|
|||||||
if let result = try await SystemExtension.uninstall() {
|
if let result = try await SystemExtension.uninstall() {
|
||||||
switch result {
|
switch result {
|
||||||
case .completed:
|
case .completed:
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Uninstall"),
|
title: String(localized: "Uninstall"),
|
||||||
message: Text("System Extension removed."),
|
message: String(localized: "System Extension removed.")
|
||||||
dismissButton: .default(Text("Ok")) {}
|
|
||||||
)
|
)
|
||||||
case .willCompleteAfterReboot:
|
case .willCompleteAfterReboot:
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Uninstall"),
|
title: String(localized: "Uninstall"),
|
||||||
message: Text("Reboot required."),
|
message: String(localized: "Reboot required.")
|
||||||
dismissButton: .default(Text("Ok")) {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public struct ServiceLogView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.navigationTitle("Service Log")
|
.navigationTitle("Service Log")
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.focusable()
|
.focusable()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import SwiftUI
|
|||||||
final class ServiceLogViewModel: ObservableObject {
|
final class ServiceLogViewModel: ObservableObject {
|
||||||
@Published var isLoading = true
|
@Published var isLoading = true
|
||||||
@Published var content = ""
|
@Published var content = ""
|
||||||
@Published var alert: Alert?
|
@Published var alert: AlertState?
|
||||||
|
|
||||||
var isEmpty: Bool {
|
var isEmpty: Bool {
|
||||||
content.isEmpty
|
content.isEmpty
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ struct EditProfileContentWindow: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text("Do you want to save the changes you made?")
|
Text("Do you want to save the changes you made?")
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.navigationTitle(navigationTitle)
|
.navigationTitle(navigationTitle)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItemGroup(placement: .navigation) {
|
ToolbarItemGroup(placement: .navigation) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public struct MainView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.onAppear(environments: environments)
|
viewModel.onAppear(environments: environments)
|
||||||
}
|
}
|
||||||
.alertBinding($viewModel.alert)
|
.alert($viewModel.alert)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigation) {
|
ToolbarItem(placement: .navigation) {
|
||||||
StartStopButton()
|
StartStopButton()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class MainViewModel: ObservableObject {
|
|||||||
@Published public var selection = NavigationPage.dashboard
|
@Published public var selection = NavigationPage.dashboard
|
||||||
@Published public var importProfile: LibboxProfileContent?
|
@Published public var importProfile: LibboxProfileContent?
|
||||||
@Published public var importRemoteProfile: LibboxImportRemoteProfile?
|
@Published public var importRemoteProfile: LibboxImportRemoteProfile?
|
||||||
@Published public var alert: Alert?
|
@Published public var alert: AlertState?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ public class MainViewModel: ObservableObject {
|
|||||||
await importURLProfile(url)
|
await importURLProfile(url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert = Alert(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)"))
|
alert = AlertState(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ public class MainViewModel: ObservableObject {
|
|||||||
importProfile = try await .from(readURL(url))
|
importProfile = try await .from(readURL(url))
|
||||||
url.stopAccessingSecurityScopedResource()
|
url.stopAccessingSecurityScopedResource()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if selection != .dashboard {
|
if selection != .dashboard {
|
||||||
@@ -78,10 +78,10 @@ public class MainViewModel: ObservableObject {
|
|||||||
private func checkApplicationPath() {
|
private func checkApplicationPath() {
|
||||||
let directoryName = URL(filePath: Bundle.main.bundlePath).deletingLastPathComponent().pathComponents.last
|
let directoryName = URL(filePath: Bundle.main.bundlePath).deletingLastPathComponent().pathComponents.last
|
||||||
if directoryName != "Applications" {
|
if directoryName != "Applications" {
|
||||||
alert = Alert(
|
alert = AlertState(
|
||||||
title: Text("Wrong application location"),
|
title: String(localized: "Wrong application location"),
|
||||||
message: Text("This app needs to be placed under the Applications folder to work."),
|
message: String(localized: "This app needs to be placed under the Applications folder to work."),
|
||||||
dismissButton: .default(Text("Ok")) {
|
dismissButton: .default(String(localized: "Ok")) {
|
||||||
NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: "")
|
NSWorkspace.shared.selectFile(Bundle.main.bundlePath, inFileViewerRootedAtPath: "")
|
||||||
NSApp.terminate(nil)
|
NSApp.terminate(nil)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public struct MenuView: View {
|
|||||||
|
|
||||||
private struct StatusSwitch: View {
|
private struct StatusSwitch: View {
|
||||||
@ObservedObject private var profile: ExtensionProfile
|
@ObservedObject private var profile: ExtensionProfile
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
init(_ profile: ExtensionProfile) {
|
init(_ profile: ExtensionProfile) {
|
||||||
self.profile = profile
|
self.profile = profile
|
||||||
@@ -88,7 +88,7 @@ public struct MenuView: View {
|
|||||||
})) {}
|
})) {}
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
.disabled(!profile.status.isEnabled)
|
.disabled(!profile.status.isEnabled)
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func switchProfile(_ isEnabled: Bool) async {
|
private func switchProfile(_ isEnabled: Bool) async {
|
||||||
@@ -99,7 +99,7 @@ public struct MenuView: View {
|
|||||||
try await profile.stop()
|
try await profile.stop()
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ public struct MenuView: View {
|
|||||||
@State private var profileList: [ProfilePreview] = []
|
@State private var profileList: [ProfilePreview] = []
|
||||||
@State private var selectedProfileID: Int64 = 0
|
@State private var selectedProfileID: Int64 = 0
|
||||||
@State private var reasserting = false
|
@State private var reasserting = false
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
|
|
||||||
private var selectedProfileIDLocal: Binding<Int64> {
|
private var selectedProfileIDLocal: Binding<Int64> {
|
||||||
$selectedProfileID.withSetter { newValue in
|
$selectedProfileID.withSetter { newValue in
|
||||||
@@ -161,7 +161,7 @@ public struct MenuView: View {
|
|||||||
selectedProfileID = await SharedPreferences.selectedProfileID.get()
|
selectedProfileID = await SharedPreferences.selectedProfileID.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func doReload() async {
|
private func doReload() async {
|
||||||
@@ -171,7 +171,7 @@ public struct MenuView: View {
|
|||||||
do {
|
do {
|
||||||
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
|
profileList = try await ProfileManager.list().map { ProfilePreview($0) }
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if profileList.isEmpty {
|
if profileList.isEmpty {
|
||||||
@@ -194,7 +194,7 @@ public struct MenuView: View {
|
|||||||
do {
|
do {
|
||||||
try await serviceReload()
|
try await serviceReload()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reasserting = false
|
reasserting = false
|
||||||
|
|||||||
+5
-5
@@ -11,7 +11,7 @@ struct MainView: View {
|
|||||||
@State private var selection = NavigationPage.dashboard
|
@State private var selection = NavigationPage.dashboard
|
||||||
@State private var importProfile: LibboxProfileContent?
|
@State private var importProfile: LibboxProfileContent?
|
||||||
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
@State private var importRemoteProfile: LibboxImportRemoteProfile?
|
||||||
@State private var alert: Alert?
|
@State private var alert: AlertState?
|
||||||
@State private var showGroups = false
|
@State private var showGroups = false
|
||||||
@State private var showConnections = false
|
@State private var showConnections = false
|
||||||
@State private var buttonState = ButtonVisibilityState()
|
@State private var buttonState = ButtonVisibilityState()
|
||||||
@@ -122,7 +122,7 @@ struct MainView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
environments.postReload()
|
environments.postReload()
|
||||||
}
|
}
|
||||||
.alertBinding($alert)
|
.alert($alert)
|
||||||
.onChangeCompat(of: scenePhase) { newValue in
|
.onChangeCompat(of: scenePhase) { newValue in
|
||||||
if newValue == .active {
|
if newValue == .active {
|
||||||
environments.postReload()
|
environments.postReload()
|
||||||
@@ -185,7 +185,7 @@ struct MainView: View {
|
|||||||
var error: NSError?
|
var error: NSError?
|
||||||
importRemoteProfile = LibboxParseRemoteProfileImportLink(url.absoluteString, &error)
|
importRemoteProfile = LibboxParseRemoteProfileImportLink(url.absoluteString, &error)
|
||||||
if let error {
|
if let error {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if selection != .dashboard {
|
if selection != .dashboard {
|
||||||
@@ -197,14 +197,14 @@ struct MainView: View {
|
|||||||
importProfile = try .from(Data(contentsOf: url))
|
importProfile = try .from(Data(contentsOf: url))
|
||||||
url.stopAccessingSecurityScopedResource()
|
url.stopAccessingSecurityScopedResource()
|
||||||
} catch {
|
} catch {
|
||||||
alert = Alert(error)
|
alert = AlertState(error: error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if selection != .dashboard {
|
if selection != .dashboard {
|
||||||
selection = .dashboard
|
selection = .dashboard
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
alert = Alert(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)"))
|
alert = AlertState(errorMessage: String(localized: "Handled unknown URL \(url.absoluteString)"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user