import SwiftUI struct SetupView: View { @Environment(\.dismiss) private var dismiss @Binding var setupComplete: Bool @State var showingIntegrations = false @State var buttonWidth: CGFloat? @State var installed = false @State var updates = false @State var integrations = false var allDone: Bool { installed && updates && integrations } var body: some View { VStack { VStack(alignment: .leading, spacing: 0) { StepView( title: .setupAgentTitle, description: .setupAgentDescription, systemImage: "lock.laptopcomputer", ) { setupButton( .setupAgentInstallButton, complete: installed, width: buttonWidth ) { installed = true Task { await LaunchAgentController().install() } } } Divider() StepView( title: .setupUpdatesTitle, description: .setupUpdatesDescription, systemImage: "network.badge.shield.half.filled", ) { setupButton( .setupUpdatesOkButton, complete: updates, width: buttonWidth ) { updates = true } } Divider() StepView( title: .setupIntegrationsTitle, description: .setupIntegrationsDescription, systemImage: "firewall", ) { setupButton( .setupIntegrationsButton, complete: integrations, width: buttonWidth ) { showingIntegrations = true } } } .onPreferenceChange(setupButton.WidthKey.self) { width in buttonWidth = width } .background(.white.opacity(0.1), in: RoundedRectangle(cornerRadius: 10)) .frame(minWidth: 600, maxWidth: .infinity) HStack { Spacer() Button(.setupDoneButton) { setupComplete = true dismiss() } .disabled(!allDone) .primaryButton() } } .interactiveDismissDisabled() .padding() .sheet(isPresented: $showingIntegrations, onDismiss: { integrations = true }, content: { IntegrationsView() }) } } struct setupButton: View { struct WidthKey: @MainActor PreferenceKey { @MainActor static var defaultValue: CGFloat? = nil static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) { if let next = nextValue(), next > (value ?? -1) { value = next } } } let label: LocalizedStringResource let complete: Bool let action: () -> Void let width: CGFloat? @State var currentWidth: CGFloat? init(_ label: LocalizedStringResource, complete: Bool, width: CGFloat? = nil, action: @escaping () -> Void) { self.label = label self.complete = complete self.action = action self.width = width } var body: some View { Button(action: action) { HStack(spacing: 6) { if complete { Text(.setupStepCompleteButton) Image(systemName: "checkmark.circle.fill") } else { Text(label) } } .frame(width: width) .padding(.vertical, 2) .onGeometryChange(for: CGFloat.self) { proxy in proxy.size.width } action: { newValue in currentWidth = newValue } } .preference(key: WidthKey.self, value: currentWidth) .primaryButton() .disabled(complete) .tint(complete ? .green : nil) } } struct StepView: View { let title: LocalizedStringResource let icon: Image let description: LocalizedStringResource let actions: Content init(title: LocalizedStringResource, description: LocalizedStringResource, systemImage: String, actions: () -> Content) { self.title = title self.icon = Image(systemName: systemImage) self.description = description self.actions = actions() } var body: some View { HStack(spacing: 0) { icon .resizable() .aspectRatio(contentMode: .fit) .frame(width: 24) Spacer() .frame(width: 20) VStack(alignment: .leading, spacing: 4) { Text(title) .bold() Text(description) } Spacer(minLength: 20) actions } .padding(20) } } extension SetupView { enum Constants { static let updaterFAQURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md#whats-this-network-request-to-github")! } } #Preview { SetupView(setupComplete: .constant(false)) }