diff --git a/Applite.xcodeproj/project.pbxproj b/Applite.xcodeproj/project.pbxproj index 4792683..7139389 100644 --- a/Applite.xcodeproj/project.pbxproj +++ b/Applite.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 413F872E2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F872D2D33E1CA00D4BE10 /* HomeView+NoSearchResults.swift */; }; 413F87302D33E29A00D4BE10 /* HomeView+SortingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F872F2D33E29A00D4BE10 /* HomeView+SortingOptions.swift */; }; 413F87322D33E30400D4BE10 /* SortingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 413F87312D33E30400D4BE10 /* SortingOptions.swift */; }; + 413F87352D34109000D4BE10 /* ButtonKit in Frameworks */ = {isa = PBXBuildFile; productRef = 413F87342D34109000D4BE10 /* ButtonKit */; }; 414074F528DF53E80073EB22 /* AppliteApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414074F428DF53E80073EB22 /* AppliteApp.swift */; }; 414074F728DF53E80073EB22 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 414074F628DF53E80073EB22 /* ContentView.swift */; }; 414074F928DF53EB0073EB22 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 414074F828DF53EB0073EB22 /* Assets.xcassets */; }; @@ -276,6 +277,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 413F87352D34109000D4BE10 /* ButtonKit in Frameworks */, 41C8FA292A7A598B000BB9A2 /* Sparkle in Frameworks */, 419256B22D26033400D9EF10 /* DebouncedOnChange in Frameworks */, 4189CE392937CD41009C836D /* Shimmer in Frameworks */, @@ -728,6 +730,7 @@ 413E60BF2BBF0E5C00978F6A /* Kingfisher */, 419256B12D26033400D9EF10 /* DebouncedOnChange */, 413F87232D32D2B100D4BE10 /* IfritStatic */, + 413F87342D34109000D4BE10 /* ButtonKit */, ); productName = Applite; productReference = 414074F128DF53E80073EB22 /* Applite.app */; @@ -768,6 +771,7 @@ 413E60BE2BBF0E5C00978F6A /* XCRemoteSwiftPackageReference "Kingfisher" */, 419256B02D26033400D9EF10 /* XCRemoteSwiftPackageReference "DebouncedOnChange" */, 413F87222D32D2B100D4BE10 /* XCRemoteSwiftPackageReference "Ifrit" */, + 413F87332D34109000D4BE10 /* XCRemoteSwiftPackageReference "ButtonKit" */, ); productRefGroup = 414074F228DF53E80073EB22 /* Products */; projectDirPath = ""; @@ -1048,7 +1052,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Applite/Preview Content\""; DEVELOPMENT_TEAM = 9CLTNBW4Z3; @@ -1083,7 +1087,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 15; + CURRENT_PROJECT_VERSION = 16; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Applite/Preview Content\""; DEVELOPMENT_TEAM = 9CLTNBW4Z3; @@ -1148,6 +1152,14 @@ minimumVersion = 2.0.3; }; }; + 413F87332D34109000D4BE10 /* XCRemoteSwiftPackageReference "ButtonKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/Dean151/ButtonKit?tab=readme-ov-file"; + requirement = { + branch = "on-button-error-modifier"; + kind = branch; + }; + }; 4189CE372937CD41009C836D /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/markiv/SwiftUI-Shimmer"; @@ -1193,6 +1205,11 @@ package = 413F87222D32D2B100D4BE10 /* XCRemoteSwiftPackageReference "Ifrit" */; productName = IfritStatic; }; + 413F87342D34109000D4BE10 /* ButtonKit */ = { + isa = XCSwiftPackageProductDependency; + package = 413F87332D34109000D4BE10 /* XCRemoteSwiftPackageReference "ButtonKit" */; + productName = ButtonKit; + }; 4189CE382937CD41009C836D /* Shimmer */ = { isa = XCSwiftPackageProductDependency; package = 4189CE372937CD41009C836D /* XCRemoteSwiftPackageReference "SwiftUI-Shimmer" */; diff --git a/Applite/Views/App Views/App View/AppView+GetInfoButton.swift b/Applite/Views/App Views/App View/AppView+GetInfoButton.swift index 0ca2084..8461ce9 100644 --- a/Applite/Views/App Views/App View/AppView+GetInfoButton.swift +++ b/Applite/Views/App Views/App View/AppView+GetInfoButton.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit import OSLog extension AppView { @@ -19,19 +20,16 @@ extension AppView { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "GetInfoButton") var body: some View { - Button { - Task { - do { - let caskInfo = try await caskManager.getAdditionalInfoForCask(cask) - openWindow(value: caskInfo) - } catch { - alert.show(error: error, title: "Failed to gather cask info") - logger.error("Failed to gather additional cask info: \(error.localizedDescription)") - } - } + AsyncButton { + let caskInfo = try await caskManager.getAdditionalInfoForCask(cask) + openWindow(value: caskInfo) } label: { Label("Get Info", systemImage: "info.circle") } + .onButtonError { error in + alert.show(error: error, title: "Failed to gather cask info") + logger.error("Failed to gather additional cask info: \(error.localizedDescription)") + } .alertManager(alert) } } diff --git a/Applite/Views/App Views/App View/AppView+OpenAndManageView.swift b/Applite/Views/App Views/App View/AppView+OpenAndManageView.swift index 4bb2d80..8caa2ad 100644 --- a/Applite/Views/App Views/App View/AppView+OpenAndManageView.swift +++ b/Applite/Views/App Views/App View/AppView+OpenAndManageView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit extension AppView { /// Button used in the Download section, launches, uninstalls or reinstalls the app @@ -22,18 +23,15 @@ extension AppView { var body: some View { // Lauch app - Button("Open") { - Task { - do { - try await cask.launchApp() - } catch { - showAppNotFoundAlert = true - } - } + AsyncButton("Open") { + try await cask.launchApp() } .font(.system(size: 14)) .buttonStyle(.bordered) .clipShape(Capsule()) + .onButtonError { error in + showAppNotFoundAlert = true + } .alert("Applite couldn't open \(cask.info.name)", isPresented: $showAppNotFoundAlert) {} if deleteButton { @@ -61,9 +59,7 @@ extension AppView { // Uninstall button Button(role: .destructive) { - Task { - caskManager.uninstall(cask) - } + caskManager.uninstall(cask) } label: { Label("Uninstall", systemImage: "trash") .foregroundStyle(.red) @@ -71,9 +67,7 @@ extension AppView { // Uninstall completely button Button(role: .destructive) { - Task { - caskManager.uninstall(cask, zap: true) - } + caskManager.uninstall(cask, zap: true) } label: { Label("Uninstall Completely", systemImage: "trash.fill") .foregroundStyle(.red) diff --git a/Applite/Views/Content View/ContentView+DetailView.swift b/Applite/Views/Content View/ContentView+DetailView.swift index 500f2f5..8fdc1be 100644 --- a/Applite/Views/Content View/ContentView+DetailView.swift +++ b/Applite/Views/Content View/ContentView+DetailView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit extension ContentView { @ViewBuilder @@ -50,10 +51,8 @@ extension ContentView { VStack(alignment: .center) { Text(DependencyManager.brokenPathOrIstallMessage) - Button { - Task { - await loadCasks() - } + AsyncButton { + await loadCasks() } label: { Label("Retry load", systemImage: "arrow.clockwise.circle") } diff --git a/Applite/Views/Content View/ContentView.swift b/Applite/Views/Content View/ContentView.swift index e6fe1bf..2e8bc22 100755 --- a/Applite/Views/Content View/ContentView.swift +++ b/Applite/Views/Content View/ContentView.swift @@ -7,6 +7,7 @@ import SwiftUI import OSLog +import ButtonKit struct ContentView: View { @EnvironmentObject var caskManager: CaskManager @@ -82,10 +83,8 @@ struct ContentView: View { } // Load failure alert .alert(loadAlert.title, isPresented: $loadAlert.isPresented) { - Button { - Task { @MainActor in - await loadCasks() - } + AsyncButton { + await loadCasks() } label: { Label("Retry", systemImage: "arrow.clockwise") } diff --git a/Applite/Views/Detail Views/App Migration/AppMigrationView+ExportView.swift b/Applite/Views/Detail Views/App Migration/AppMigrationView+ExportView.swift index 807d526..deed23d 100644 --- a/Applite/Views/Detail Views/App Migration/AppMigrationView+ExportView.swift +++ b/Applite/Views/Detail Views/App Migration/AppMigrationView+ExportView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit import OSLog extension AppMigrationView { @@ -23,19 +24,16 @@ extension AppMigrationView { .font(.appliteSmallTitle) HStack { - Button { - Task { - do { - exportFile = try await AppMigration.export() - showFileExporter = true - } catch { - alert.show(error: error, title: "Failed to export") - } - } + AsyncButton { + exportFile = try await AppMigration.export() + showFileExporter = true } label: { Label("Export Apps to File", systemImage: "square.and.arrow.up") } .controlSize(.large) + .onButtonError { error in + alert.show(error: error, title: "Failed to export") + } if exportSuccessful { Image(systemName: "square.and.arrow.down.badge.checkmark") diff --git a/Applite/Views/Detail Views/Brew Management/BrewManagementView+ActionsView.swift b/Applite/Views/Detail Views/Brew Management/BrewManagementView+ActionsView.swift index 64e92a9..9288b56 100644 --- a/Applite/Views/Detail Views/Brew Management/BrewManagementView+ActionsView.swift +++ b/Applite/Views/Detail Views/Brew Management/BrewManagementView+ActionsView.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit extension BrewManagementView { struct ActionsView: View { @@ -122,36 +123,18 @@ extension BrewManagementView { @MainActor private var updateButton: some View { - Button { - withAnimation { - modifyingBrew = true - } - - Task { - logger.info("Updating brew started") - - do { - try await Shell.runBrewCommand(["update"]) - } catch { - logger.error("Brew update failed. Error: \(error.localizedDescription)") - updateFailed = true - } - - logger.info("Brew update successful") - - updateDone = true - - withAnimation { - modifyingBrew = false - } - - } + AsyncButton { + try await updateHomebrew() } label: { Label("Update Homebrew", systemImage: "arrow.uturn.down.circle") } .controlSize(.large) .disabled(modifyingBrew) .padding(.trailing, 3) + .onButtonError { error in + logger.error("Brew update failed. Error: \(error.localizedDescription)") + updateFailed = true + } .alert("Update failed", isPresented: $updateFailed, actions: {}) } @@ -199,5 +182,23 @@ extension BrewManagementView { Button("OK", role: .cancel) { } }) } + + func updateHomebrew() async throws { + withAnimation { + modifyingBrew = true + } + + logger.info("Updating brew started") + + try await Shell.runBrewCommand(["update"]) + + logger.info("Brew update successful") + + updateDone = true + + withAnimation { + modifyingBrew = false + } + } } } diff --git a/Applite/Views/Detail Views/Update/UpdateView+ToolbarItems.swift b/Applite/Views/Detail Views/Update/UpdateView+ToolbarItems.swift index 4557fba..a4f1452 100644 --- a/Applite/Views/Detail Views/Update/UpdateView+ToolbarItems.swift +++ b/Applite/Views/Detail Views/Update/UpdateView+ToolbarItems.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit extension UpdateView { var toolbarItems: some ToolbarContent { @@ -29,14 +30,11 @@ extension UpdateView { } .labelStyle(.titleAndIcon) .alert("Notice", isPresented: $showingGreedyUpdateConfirm) { - Button("Show All") { - Task { - do { - try await caskManager.refreshOutdated(greedy: true) - } catch { - loadAlert.show(title: "Failed to load updates", message: error.localizedDescription) - } - } + AsyncButton("Show All") { + try await caskManager.refreshOutdated(greedy: true) + } + .onButtonError { error in + loadAlert.show(title: "Failed to load updates", message: error.localizedDescription) } Button("Cancel", role: .cancel) {} @@ -49,20 +47,15 @@ extension UpdateView { } private var refreshButton: some View { - Button { - Task { - refreshing = true - - do { - try await caskManager.refreshOutdated() - } catch { - loadAlert.show(title: "Failed to refresh updates", message: error.localizedDescription) - } - - refreshing = false - } + AsyncButton { + refreshing = true + try await caskManager.refreshOutdated() + refreshing = false } label: { Image(systemName: "arrow.clockwise") } + .onButtonError { error in + loadAlert.show(title: "Failed to refresh updates", message: error.localizedDescription) + } } } diff --git a/Applite/Views/Setup/SetupView+AppliteBrewInstall.swift b/Applite/Views/Setup/SetupView+AppliteBrewInstall.swift index 9817000..ea6a08a 100644 --- a/Applite/Views/Setup/SetupView+AppliteBrewInstall.swift +++ b/Applite/Views/Setup/SetupView+AppliteBrewInstall.swift @@ -6,6 +6,7 @@ // import SwiftUI +import ButtonKit extension SetupView { /// Brew installation page @@ -43,10 +44,8 @@ extension SetupView { // Retry button if failed { - Button { - Task { - await installDependencies() - } + AsyncButton { + await installDependencies() } label: { Label("Retry Install", systemImage: "arrow.clockwise.circle") }