From 4ddfcdb3242466ab9f1273940a61cd7d239ad2b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=96=E7=95=8C?= Date: Mon, 1 Dec 2025 17:32:23 +0800 Subject: [PATCH] Refactor Alerts --- ApplicationLibrary/Views/Abstract/Alert.swift | 47 ------ .../Views/Abstract/AlertState.swift | 156 ++++++++++++++++++ .../Views/Abstract/BaseViewModel.swift | 8 +- .../Views/Abstract/ShareButton.swift | 16 +- .../Connections/ConnectionListView.swift | 2 +- .../Connections/ConnectionListViewModel.swift | 4 +- .../Views/Connections/ConnectionView.swift | 6 +- .../Views/Dashboard/ActiveDashboardView.swift | 4 +- .../Views/Dashboard/Cards/ClashModeCard.swift | 6 +- .../Views/Dashboard/Cards/ProfileCard.swift | 12 +- .../Components/ExtensionStatusView.swift | 4 +- .../Components/InstallProfileButton.swift | 6 +- .../InstallSystemExtensionButton.swift | 8 +- .../Components/StartStopButton.swift | 6 +- .../Views/Dashboard/DashboardView.swift | 20 +-- .../Views/Dashboard/DashboardViewModel.swift | 59 +++---- .../Dashboard/Overview/OverviewView.swift | 2 +- .../Overview/OverviewViewModel.swift | 4 +- .../Views/Groups/GroupListView.swift | 2 +- .../Views/Groups/GroupListViewModel.swift | 8 +- ApplicationLibrary/Views/Log/LogView.swift | 8 +- .../Views/Log/LogViewModel.swift | 4 +- .../Profile/EditProfileContentView.swift | 2 +- .../Profile/EditProfileContentViewModel.swift | 4 +- .../Views/Profile/EditProfileView.swift | 4 +- .../Views/Profile/EditProfileViewModel.swift | 6 +- .../Views/Profile/ImportProfileView.swift | 2 +- .../Profile/ImportProfileViewModel.swift | 6 +- .../Views/Profile/NewProfileView.swift | 8 +- .../Views/Profile/NewProfileViewModel.swift | 8 +- .../Views/Profile/ProfileView.swift | 2 +- .../Views/Profile/ProfileViewModel.swift | 30 ++-- .../Views/Setting/MacAppView.swift | 38 ++--- .../Views/Setting/ServiceLogView.swift | 2 +- .../Views/Setting/ServiceLogViewModel.swift | 2 +- MacLibrary/EditProfileContentWindow.swift | 2 +- MacLibrary/MainView.swift | 2 +- MacLibrary/MainViewModel.swift | 14 +- MacLibrary/MenuView.swift | 14 +- SFI/MainView.swift | 10 +- 40 files changed, 324 insertions(+), 224 deletions(-) delete mode 100644 ApplicationLibrary/Views/Abstract/Alert.swift create mode 100644 ApplicationLibrary/Views/Abstract/AlertState.swift diff --git a/ApplicationLibrary/Views/Abstract/Alert.swift b/ApplicationLibrary/Views/Abstract/Alert.swift deleted file mode 100644 index 02a4bd9..0000000 --- a/ApplicationLibrary/Views/Abstract/Alert.swift +++ /dev/null @@ -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) -> some View { - alert(isPresented: Binding(get: { - binding.wrappedValue != nil - }, set: { newValue, _ in - if !newValue { - binding.wrappedValue = nil - } - })) { - binding.wrappedValue! - } - } - - func alertBinding(_ binding: Binding, _ isLoading: Binding) -> some View { - alert(isPresented: Binding(get: { - binding.wrappedValue != nil - }, set: { newValue, _ in - if !newValue, !isLoading.wrappedValue { - binding.wrappedValue = nil - } - })) { - binding.wrappedValue! - } - } -} diff --git a/ApplicationLibrary/Views/Abstract/AlertState.swift b/ApplicationLibrary/Views/Abstract/AlertState.swift new file mode 100644 index 0000000..ab6e3df --- /dev/null +++ b/ApplicationLibrary/Views/Abstract/AlertState.swift @@ -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) -> 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, isLoading: Binding) -> 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) + } + } +} diff --git a/ApplicationLibrary/Views/Abstract/BaseViewModel.swift b/ApplicationLibrary/Views/Abstract/BaseViewModel.swift index 2d74020..de70e3f 100644 --- a/ApplicationLibrary/Views/Abstract/BaseViewModel.swift +++ b/ApplicationLibrary/Views/Abstract/BaseViewModel.swift @@ -2,20 +2,20 @@ import SwiftUI @MainActor open class BaseViewModel: ObservableObject { - @Published public var alert: Alert? + @Published public var alert: AlertState? @Published public var isLoading = false public init() {} public func showError(_ error: Error) { - alert = Alert(error) + alert = AlertState(error: error) } public func execute(_ operation: () async throws -> Void) async { do { try await operation() } catch { - alert = Alert(error) + alert = AlertState(error: error) } } @@ -24,7 +24,7 @@ open class BaseViewModel: ObservableObject { try await operation() } catch { await MainActor.run { - alert = Alert(error) + alert = AlertState(error: error) } } } diff --git a/ApplicationLibrary/Views/Abstract/ShareButton.swift b/ApplicationLibrary/Views/Abstract/ShareButton.swift index 4cdbadd..1721bbb 100644 --- a/ApplicationLibrary/Views/Abstract/ShareButton.swift +++ b/ApplicationLibrary/Views/Abstract/ShareButton.swift @@ -9,11 +9,11 @@ import SwiftUI @MainActor public struct ProfileShareButton