~ Refactoring of install process

This commit is contained in:
David Bureš 2025-04-26 15:09:07 +02:00
parent 35eeb39096
commit 6e569376a1
11 changed files with 179 additions and 116 deletions

View File

@ -7,7 +7,17 @@
import Foundation
enum PackageInstallationProcessSteps
enum PackageInstallationProcessSteps: Equatable
{
case ready, searching, presentingSearchResults, installing, finished, fatalError, requiresSudoPassword, wrongArchitecture, binaryAlreadyExists, anotherProcessAlreadyRunning, installationTerminatedUnexpectedly
case ready
case searching
case presentingSearchResults
case installing(packageToInstall: BrewPackage)
case finished
case fatalError(packageThatWasGettingInstalled: BrewPackage)
case requiresSudoPassword(packageThatWasGettingInstalled: BrewPackage)
case wrongArchitecture(packageThatWasGettingInstalled: BrewPackage)
case binaryAlreadyExists(packageThatWasGettingInstalled: BrewPackage)
case anotherProcessAlreadyRunning
case installationTerminatedUnexpectedly
}

View File

@ -0,0 +1,13 @@
//
// Package Install Initialization Error.swift
// Cork
//
// Created by David Bureš - P on 22.04.2025.
//
import Foundation
enum PackageInstallationInitializationError: Error
{
case couldNotStartInstallProcessWithPackage(package: BrewPackage?)
}

View File

@ -10,7 +10,10 @@ import CorkShared
class InstallationProgressTracker: ObservableObject
{
@Published var packageBeingInstalled: PackageInProgressOfBeingInstalled = .init(package: .init(name: "", type: .formula, installedOn: nil, versions: [], sizeInBytes: 0, downloadCount: nil), installationStage: .downloadingCask, packageInstallationProgress: 0)
@Published var installationStage: PackageInstallationStage = .downloadingCask
@Published var installationProgress: Double = 0
@Published var realTimeTerminalOutput: [RealTimeTerminalLine] = .init()
@Published var numberOfPackageDependencies: Int = 0
@Published var numberInLineOfPackageCurrentlyBeingFetched: Int = 0
@ -38,30 +41,28 @@ class InstallationProgressTracker: ObservableObject
}
@MainActor
func installPackage(using brewData: BrewDataStorage, cachedPackagesTracker: CachedPackagesTracker) async throws -> TerminalOutput
func installPackage(packageToInstall: BrewPackage, using brewData: BrewDataStorage, cachedPackagesTracker: CachedPackagesTracker) async throws -> TerminalOutput
{
let package: BrewPackage = packageBeingInstalled.package
AppConstants.shared.logger.debug("Installing package \(package.name, privacy: .auto)")
AppConstants.shared.logger.debug("Installing package \(packageToInstall.name, privacy: .auto)")
var installationResult: TerminalOutput = .init(standardOutput: "", standardError: "")
if package.type == .formula
if packageToInstall.type == .formula
{
AppConstants.shared.logger.info("Package \(package.name, privacy: .public) is Formula")
AppConstants.shared.logger.info("Package \(packageToInstall.name, privacy: .public) is Formula")
let output: String = try await installFormula(using: brewData).joined(separator: "")
let output: String = try await installFormula(packageToInstall).joined(separator: "")
installationResult.standardOutput.append(output)
packageBeingInstalled.packageInstallationProgress = 10
installationProgress = 10
packageBeingInstalled.installationStage = .finished
installationStage = .finished
}
else
{
AppConstants.shared.logger.info("Package is Cask")
try await installCask(using: brewData)
try await installCask(packageToInstall)
}
do
@ -77,17 +78,16 @@ class InstallationProgressTracker: ObservableObject
}
@MainActor
private func installFormula(using _: BrewDataStorage) async throws -> [String]
private func installFormula(_ packageToInstall: BrewPackage) async throws -> [String]
{
let package: BrewPackage = packageBeingInstalled.package
var packageDependencies: [String] = .init()
/// For some reason, the line `fetching [package name]` appears twice during the matching process, and the first one is a dud. Ignore that first one.
var hasAlreadyMatchedLineAboutInstallingPackageItself: Bool = false
var installOutput: [String] = .init()
AppConstants.shared.logger.info("Package \(package.name, privacy: .public) is Formula")
AppConstants.shared.logger.info("Package \(packageToInstall.name, privacy: .public) is Formula")
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", package.name])
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", packageToInstall.name])
installationProcess = process
for await output in stream
{
@ -99,7 +99,7 @@ class InstallationProgressTracker: ObservableObject
if showRealTimeTerminalOutputs
{
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine))
realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine))
}
AppConstants.shared.logger.info("Does the line contain an element from the array? \(outputLine.containsElementFromArray(packageDependencies), privacy: .public)")
@ -107,7 +107,7 @@ class InstallationProgressTracker: ObservableObject
if outputLine.contains("Fetching dependencies")
{
// First, we have to get a list of all the dependencies
var matchedDependencies: String = try outputLine.regexMatch("(?<=\(package.name): ).*?(.*)")
var matchedDependencies: String = try outputLine.regexMatch("(?<=\(packageToInstall.name): ).*?(.*)")
matchedDependencies = matchedDependencies.replacingOccurrences(of: " and", with: ",") // The last dependency is different, because it's preceded by "and" instead of "," so let's replace that "and" with "," so we can split it nicely
AppConstants.shared.logger.debug("Matched Dependencies: \(matchedDependencies, privacy: .auto)")
@ -120,43 +120,43 @@ class InstallationProgressTracker: ObservableObject
numberOfPackageDependencies = packageDependencies.count // Assign the number of dependencies to the tracker for the user to see
packageBeingInstalled.packageInstallationProgress = 1
installationProgress = 1
}
else if outputLine.contains("Installing dependencies") || outputLine.contains("Installing \(package.name) dependency")
else if outputLine.contains("Installing dependencies") || outputLine.contains("Installing \(packageToInstall.name) dependency")
{
AppConstants.shared.logger.info("Will install dependencies!")
packageBeingInstalled.installationStage = .installingDependencies
installationStage = .installingDependencies
// Increment by 1 for each package that finished installing
numberInLineOfPackageCurrentlyBeingInstalled = numberInLineOfPackageCurrentlyBeingInstalled + 1
AppConstants.shared.logger.info("Installing dependency \(self.numberInLineOfPackageCurrentlyBeingInstalled) of \(packageDependencies.count)")
// TODO: Add a math formula for advancing the stepper
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + Double(Double(10) / (Double(3) * Double(numberOfPackageDependencies)))
installationProgress = installationProgress + Double(Double(10) / (Double(3) * Double(numberOfPackageDependencies)))
}
else if outputLine.contains("Already downloaded") || (outputLine.contains("Fetching") && outputLine.containsElementFromArray(packageDependencies))
{
AppConstants.shared.logger.info("Will fetch dependencies!")
packageBeingInstalled.installationStage = .fetchingDependencies
installationStage = .fetchingDependencies
numberInLineOfPackageCurrentlyBeingFetched = numberInLineOfPackageCurrentlyBeingFetched + 1
AppConstants.shared.logger.info("Fetching dependency \(self.numberInLineOfPackageCurrentlyBeingFetched) of \(packageDependencies.count)")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + Double(Double(10) / (Double(3) * (Double(numberOfPackageDependencies) * Double(5))))
installationProgress = installationProgress + Double(Double(10) / (Double(3) * (Double(numberOfPackageDependencies) * Double(5))))
}
else if outputLine.contains("Fetching \(package.name)") || outputLine.contains("Installing \(package.name)")
else if outputLine.contains("Fetching \(packageToInstall.name)") || outputLine.contains("Installing \(packageToInstall.name)")
{
if hasAlreadyMatchedLineAboutInstallingPackageItself
{ /// Only the second line about the package being installed is valid
AppConstants.shared.logger.info("Will install the package itself!")
packageBeingInstalled.installationStage = .installingPackage
installationStage = .installingPackage
// TODO: Add a math formula for advancing the stepper
packageBeingInstalled.packageInstallationProgress = Double(packageBeingInstalled.packageInstallationProgress) + Double((Double(10) - Double(packageBeingInstalled.packageInstallationProgress)) / Double(2))
installationProgress = Double(installationProgress) + Double((Double(10) - Double(installationProgress)) / Double(2))
AppConstants.shared.logger.info("Stepper value: \(Double(Double(10) / (Double(3) * Double(self.numberOfPackageDependencies))))")
}
@ -164,47 +164,45 @@ class InstallationProgressTracker: ObservableObject
{ /// When it appears for the first time, ignore it
AppConstants.shared.logger.info("Matched the dud line about the package itself being installed!")
hasAlreadyMatchedLineAboutInstallingPackageItself = true
packageBeingInstalled.packageInstallationProgress = Double(packageBeingInstalled.packageInstallationProgress) + Double((Double(10) - Double(packageBeingInstalled.packageInstallationProgress)) / Double(2))
installationProgress = Double(installationProgress) + Double((Double(10) - Double(installationProgress)) / Double(2))
}
}
installOutput.append(outputLine)
AppConstants.shared.logger.debug("Current installation stage: \(self.packageBeingInstalled.installationStage.description, privacy: .public)")
AppConstants.shared.logger.debug("Current installation stage: \(self.installationStage.description, privacy: .public)")
case .standardError(let errorLine):
AppConstants.shared.logger.error("Errored out: \(errorLine, privacy: .public)")
if showRealTimeTerminalOutputs
{
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLine))
realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLine))
}
if errorLine.contains("a password is required")
{
AppConstants.shared.logger.warning("Install requires sudo")
packageBeingInstalled.installationStage = .requiresSudoPassword
installationStage = .requiresSudoPassword
}
}
}
packageBeingInstalled.packageInstallationProgress = 10
installationProgress = 10
packageBeingInstalled.installationStage = .finished
installationStage = .finished
return installOutput
}
@MainActor
func installCask(using _: BrewDataStorage) async throws
func installCask(_ packageToInstall: BrewPackage) async throws
{
let package: BrewPackage = packageBeingInstalled.package
AppConstants.shared.logger.info("Package is Cask")
AppConstants.shared.logger.debug("Installing package \(package.name, privacy: .public)")
AppConstants.shared.logger.debug("Installing package \(packageToInstall.name, privacy: .public)")
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", "--no-quarantine", package.name])
let (stream, process): (AsyncStream<StreamedTerminalOutput>, Process) = shell(AppConstants.shared.brewExecutablePath, ["install", "--no-quarantine", packageToInstall.name])
installationProcess = process
for await output in stream
{
@ -215,56 +213,56 @@ class InstallationProgressTracker: ObservableObject
if showRealTimeTerminalOutputs
{
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine))
realTimeTerminalOutput.append(RealTimeTerminalLine(line: outputLine))
}
if outputLine.contains("Downloading")
{
AppConstants.shared.logger.info("Will download Cask")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 2
installationProgress = installationProgress + 2
packageBeingInstalled.installationStage = .downloadingCask
installationStage = .downloadingCask
}
else if outputLine.contains("Installing Cask")
{
AppConstants.shared.logger.info("Will install Cask")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 2
installationProgress = installationProgress + 2
packageBeingInstalled.installationStage = .installingCask
installationStage = .installingCask
}
else if outputLine.contains("Moving App")
{
AppConstants.shared.logger.info("Moving App")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 2
installationProgress = installationProgress + 2
packageBeingInstalled.installationStage = .movingCask
installationStage = .movingCask
}
else if outputLine.contains("Linking binary")
{
AppConstants.shared.logger.info("Linking Binary")
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 2
installationProgress = installationProgress + 2
packageBeingInstalled.installationStage = .linkingCaskBinary
installationStage = .linkingCaskBinary
}
else if outputLine.contains("Purging files")
{
AppConstants.shared.logger.info("Purging old version of cask \(package.name)")
AppConstants.shared.logger.info("Purging old version of cask \(packageToInstall.name)")
packageBeingInstalled.installationStage = .installingCask
installationStage = .installingCask
packageBeingInstalled.packageInstallationProgress = packageBeingInstalled.packageInstallationProgress + 1
installationProgress = installationProgress + 1
}
else if outputLine.contains("was successfully installed")
{
AppConstants.shared.logger.info("Finished installing app")
packageBeingInstalled.installationStage = .finished
installationStage = .finished
packageBeingInstalled.packageInstallationProgress = 10
installationProgress = 10
}
case .standardError(let errorLine):
@ -272,26 +270,26 @@ class InstallationProgressTracker: ObservableObject
if showRealTimeTerminalOutputs
{
packageBeingInstalled.realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLine))
realTimeTerminalOutput.append(RealTimeTerminalLine(line: errorLine))
}
if errorLine.contains("a password is required")
{
AppConstants.shared.logger.warning("Install requires sudo")
packageBeingInstalled.installationStage = .requiresSudoPassword
installationStage = .requiresSudoPassword
}
else if errorLine.contains("there is already an App at")
{
AppConstants.shared.logger.warning("The app already exists")
packageBeingInstalled.installationStage = .binaryAlreadyExists
installationStage = .binaryAlreadyExists
}
else if errorLine.contains(/depends on hardware architecture being.+but you are running/)
{
AppConstants.shared.logger.warning("Package is wrong architecture")
packageBeingInstalled.installationStage = .wrongArchitecture
installationStage = .wrongArchitecture
}
}
}

View File

@ -5,10 +5,10 @@
// Created by David Bureš on 03.07.2022.
//
import ButtonKit
import CorkNotifications
import CorkShared
import SwiftUI
import ButtonKit
struct AddFormulaView: View
{
@ -42,7 +42,32 @@ struct AddFormulaView: View
var isDismissable: Bool
{
[.ready, .presentingSearchResults, .fatalError, .anotherProcessAlreadyRunning, .binaryAlreadyExists, .requiresSudoPassword, .wrongArchitecture, .anotherProcessAlreadyRunning, .installationTerminatedUnexpectedly, .installing].contains(packageInstallationProcessStep)
if case .installing = packageInstallationProcessStep
{
return true
}
if case .binaryAlreadyExists = packageInstallationProcessStep
{
return true
}
if case .fatalError = packageInstallationProcessStep
{
return true
}
if case .wrongArchitecture = packageInstallationProcessStep
{
return true
}
if case .requiresSudoPassword = packageInstallationProcessStep
{
return true
}
return [.ready, .presentingSearchResults, .anotherProcessAlreadyRunning, .anotherProcessAlreadyRunning, .installationTerminatedUnexpectedly].contains(packageInstallationProcessStep)
}
var sheetTitle: LocalizedStringKey
@ -109,32 +134,40 @@ struct AddFormulaView: View
installationProgressTracker: installationProgressTracker
)
case .installing:
case .installing(let packageToInstall):
InstallingPackageView(
installationProgressTracker: installationProgressTracker,
packageToInstall: packageToInstall,
packageInstallationProcessStep: $packageInstallationProcessStep
)
case .finished:
InstallationFinishedSuccessfullyView()
case .fatalError: /// This shows up when the function for executing the install action throws an error
InstallationFatalErrorView(installationProgressTracker: installationProgressTracker)
case .fatalError(let packageThatWasGettingInstalled): /// This shows up when the function for executing the install action throws an error
InstallationFatalErrorView(
installationProgressTracker: installationProgressTracker,
packageThatWasGettingInstalled: packageThatWasGettingInstalled
)
case .requiresSudoPassword:
SudoRequiredView(installationProgressTracker: installationProgressTracker)
case .requiresSudoPassword(let packageThatWasGettingInstalled):
SudoRequiredView(
packageThatWasGettingInstalled: packageThatWasGettingInstalled
)
case .wrongArchitecture:
WrongArchitectureView(installationProgressTracker: installationProgressTracker)
case .wrongArchitecture(let packageThatWasGettingInstalled):
WrongArchitectureView(
packageThatWasGettingInstalled: packageThatWasGettingInstalled
)
case .binaryAlreadyExists:
BinaryAlreadyExistsView(installationProgressTracker: installationProgressTracker)
case .binaryAlreadyExists(let packageThatWasGettingInstalled):
BinaryAlreadyExistsView(installationProgressTracker: installationProgressTracker, packageThatWasGettingInstalled: packageThatWasGettingInstalled)
case .anotherProcessAlreadyRunning:
AnotherProcessAlreadyRunningView()
case .installationTerminatedUnexpectedly:
InstallationTerminatedUnexpectedlyView(terminalOutputOfTheInstallation: installationProgressTracker.packageBeingInstalled.realTimeTerminalOutput)
InstallationTerminatedUnexpectedlyView(terminalOutputOfTheInstallation: installationProgressTracker.realTimeTerminalOutput)
}
}
.navigationTitle(sheetTitle)

View File

@ -125,11 +125,10 @@ struct InstallationInitialView: View
return
}
installationProgressTracker.packageBeingInstalled = PackageInProgressOfBeingInstalled(package: packageToInstall, installationStage: .ready, packageInstallationProgress: 0)
AppConstants.shared.logger.debug("Packages to install: \(installationProgressTracker.packageBeingInstalled.package.name, privacy: .public)")
AppConstants.shared.logger.debug("Packages to install: \(packageToInstall.name, privacy: .public)")
packageInstallationProcessStep = .installing
packageInstallationProcessStep = .installing(packageToInstall: packageToInstall)
} label: {
Text("add-package.install.action")

View File

@ -5,8 +5,8 @@
// Created by David Bureš on 29.09.2023.
//
import SwiftUI
import CorkShared
import SwiftUI
struct InstallingPackageView: View
{
@ -19,6 +19,8 @@ struct InstallingPackageView: View
@ObservedObject var installationProgressTracker: InstallationProgressTracker
let packageToInstall: BrewPackage
@Binding var packageInstallationProcessStep: PackageInstallationProcessSteps
@State var isShowingRealTimeOutput: Bool = false
@ -27,13 +29,13 @@ struct InstallingPackageView: View
{
VStack(alignment: .leading)
{
if installationProgressTracker.packageBeingInstalled.installationStage != .finished
if installationProgressTracker.installationStage != .finished
{
ProgressView(value: installationProgressTracker.packageBeingInstalled.packageInstallationProgress, total: 10)
ProgressView(value: installationProgressTracker.installationProgress, total: 10)
{
VStack(alignment: .leading)
{
switch installationProgressTracker.packageBeingInstalled.installationStage
switch installationProgressTracker.installationStage
{
case .ready:
Text("add-package.install.ready")
@ -56,36 +58,36 @@ struct InstallingPackageView: View
// CASKS
case .downloadingCask:
Text("add-package.install.downloading-cask-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.downloading-cask-\(packageToInstall.name)")
case .installingCask:
Text("add-package.install.installing-cask-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.installing-cask-\(packageToInstall.name)")
case .linkingCaskBinary:
Text("add-package.install.linking-cask-binary")
case .movingCask:
Text("add-package.install.moving-cask-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.moving-cask-\(packageToInstall.name)")
case .requiresSudoPassword:
Text("add-package.install.requires-sudo-password-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.requires-sudo-password-\(packageToInstall.name)")
.onAppear
{
packageInstallationProcessStep = .requiresSudoPassword
packageInstallationProcessStep = .requiresSudoPassword(packageThatWasGettingInstalled: packageToInstall)
}
case .wrongArchitecture:
Text("add-package.install.wrong-architecture.title")
.onAppear
{
packageInstallationProcessStep = .wrongArchitecture
packageInstallationProcessStep = .wrongArchitecture(packageThatWasGettingInstalled: packageToInstall)
}
case .binaryAlreadyExists:
Text("add-package.install.binary-already-exists-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.binary-already-exists-\(packageToInstall.name)")
.onAppear
{
packageInstallationProcessStep = .binaryAlreadyExists
packageInstallationProcessStep = .binaryAlreadyExists(packageThatWasGettingInstalled: packageToInstall)
}
case .terminatedUnexpectedly:
@ -96,7 +98,7 @@ struct InstallingPackageView: View
}
}
LiveTerminalOutputView(
lineArray: $installationProgressTracker.packageBeingInstalled.realTimeTerminalOutput,
lineArray: $installationProgressTracker.realTimeTerminalOutput,
isRealTimeTerminalOutputExpanded: $isShowingRealTimeOutput
)
}
@ -117,19 +119,19 @@ struct InstallingPackageView: View
do
{
let installationResult: TerminalOutput = try await installationProgressTracker.installPackage(
using: brewData,
packageToInstall: packageToInstall, using: brewData,
cachedPackagesTracker: cachedPackagesTracker
)
AppConstants.shared.logger.debug("Installation result:\nStandard output: \(installationResult.standardOutput, privacy: .public)\nStandard error: \(installationResult.standardError, privacy: .public)")
/// Check if the package installation stag at the end of the install process was something unexpected. Normal package installations go through multiple steps, and the three listed below are not supposed to be the end state. This means that something went wrong during the installation
let installationStage: PackageInstallationStage = installationProgressTracker.packageBeingInstalled.installationStage
let installationStage: PackageInstallationStage = installationProgressTracker.installationStage
if [.installingCask, .installingPackage, .ready].contains(installationStage)
{
AppConstants.shared.logger.warning("The installation process quit before it was supposed to")
installationProgressTracker.packageBeingInstalled.installationStage = .terminatedUnexpectedly
installationProgressTracker.installationStage = .terminatedUnexpectedly
}
}
catch let fatalInstallationError

View File

@ -7,6 +7,7 @@
import CorkShared
import SwiftUI
import ButtonKit
struct PresentingSearchResultsView: View
{
@ -149,12 +150,19 @@ struct PresentingSearchResultsView: View
@ViewBuilder
var startInstallProcessButton: some View
{
Button
// This has to be an AsyncButton so it shakes
AsyncButton
{
packageInstallationProcessStep = .installing
guard let packageToInstall = foundPackageSelection else
{
throw PackageInstallationInitializationError.couldNotStartInstallProcessWithPackage(package: nil)
}
packageInstallationProcessStep = .installing(packageToInstall: packageToInstall)
} label: {
Text("add-package.install.action")
}
.throwableButtonStyle(.shake)
.keyboardShortcut(.defaultAction)
.disabled(foundPackageSelection == nil)
}

View File

@ -17,6 +17,8 @@ struct BinaryAlreadyExistsView: View, Sendable
@ObservedObject var installationProgressTracker: InstallationProgressTracker
let packageThatWasGettingInstalled: BrewPackage
var body: some View
{
ComplexWithImage(image: Image(localURL: URL(filePath: "/System/Library/CoreServices/KeyboardSetupAssistant.app/Contents/Resources/AppIcon.icns"))!)
@ -24,7 +26,7 @@ struct BinaryAlreadyExistsView: View, Sendable
VStack(alignment: .leading, spacing: 10)
{
HeadlineWithSubheadline(
headline: "add-package.install.binary-already-exists-\(installationProgressTracker.packageBeingInstalled.package.name)",
headline: "add-package.install.binary-already-exists-\(packageThatWasGettingInstalled.name)",
subheadline: "add-package.install.binary-already-exists.subheadline",
alignment: .leading
)

View File

@ -11,12 +11,14 @@ struct InstallationFatalErrorView: View
{
@ObservedObject var installationProgressTracker: InstallationProgressTracker
let packageThatWasGettingInstalled: BrewPackage
var body: some View
{
ComplexWithIcon(systemName: "exclamationmark.triangle")
{
HeadlineWithSubheadline(
headline: "add-package.fatal-error-\(installationProgressTracker.packageBeingInstalled.package.name)",
headline: "add-package.fatal-error-\(packageThatWasGettingInstalled.name)",
subheadline: "add-package.fatal-error.description",
alignment: .leading
)

View File

@ -14,7 +14,7 @@ struct SudoRequiredView: View, Sendable
@EnvironmentObject var appState: AppState
@EnvironmentObject var brewData: BrewDataStorage
@ObservedObject var installationProgressTracker: InstallationProgressTracker
let packageThatWasGettingInstalled: BrewPackage
var body: some View
{
@ -24,14 +24,14 @@ struct SudoRequiredView: View, Sendable
{
VStack(alignment: .leading, spacing: 10)
{
Text("add-package.install.requires-sudo-password-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add-package.install.requires-sudo-password-\(packageThatWasGettingInstalled.name)")
.font(.headline)
ManualInstallInstructions(installationProgressTracker: installationProgressTracker)
manualInstallInstructions
}
}
Text("add.package.install.requires-sudo-password.terminal-instructions-\(installationProgressTracker.packageBeingInstalled.package.name)")
Text("add.package.install.requires-sudo-password.terminal-instructions-\(packageThatWasGettingInstalled.name)")
.font(.subheadline)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
@ -51,19 +51,15 @@ struct SudoRequiredView: View, Sendable
}
.fixedSize()
}
}
private struct ManualInstallInstructions: View
{
let installationProgressTracker: InstallationProgressTracker
@ViewBuilder
var manualInstallInstructions: some View
{
var manualInstallCommand: String
{
return "brew install \(installationProgressTracker.packageBeingInstalled.package.type == .cask ? "--cask" : "") \(installationProgressTracker.packageBeingInstalled.package.name)"
return "brew install \(packageThatWasGettingInstalled.type == .cask ? "--cask" : "") \(packageThatWasGettingInstalled.name)"
}
var body: some View
{
VStack
{
Text("add-package.install.requires-sudo-password.description")

View File

@ -14,7 +14,7 @@ struct WrongArchitectureView: View, Sendable
@EnvironmentObject var appState: AppState
@EnvironmentObject var brewData: BrewDataStorage
@ObservedObject var installationProgressTracker: InstallationProgressTracker
let packageThatWasGettingInstalled: BrewPackage
var body: some View
{
@ -24,7 +24,7 @@ struct WrongArchitectureView: View, Sendable
{
HeadlineWithSubheadline(
headline: "add-package.install.wrong-architecture.title",
subheadline: "add-package.install.wrong-architecture-\(installationProgressTracker.packageBeingInstalled.package.name).user-architecture-is-\(ProcessInfo().CPUArchitecture == .arm ? "Apple Silicon" : "Intel")",
subheadline: "add-package.install.wrong-architecture-\(packageThatWasGettingInstalled.name).user-architecture-is-\(ProcessInfo().CPUArchitecture == .arm ? "Apple Silicon" : "Intel")",
alignment: .leading
)
}