fix: avoid Swift compiler crash in onboarding wizard
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import Observation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension OnboardingView {
|
extension OnboardingView {
|
||||||
@@ -13,43 +14,10 @@ extension OnboardingView {
|
|||||||
.frame(maxWidth: 520)
|
.frame(maxWidth: 520)
|
||||||
|
|
||||||
self.onboardingCard(spacing: 14, padding: 16) {
|
self.onboardingCard(spacing: 14, padding: 16) {
|
||||||
if let error = self.onboardingWizard.errorMessage {
|
OnboardingWizardCardContent(
|
||||||
Text("Wizard error")
|
wizard: self.onboardingWizard,
|
||||||
.font(.headline)
|
|
||||||
Text(error)
|
|
||||||
.font(.subheadline)
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
|
||||||
Button("Retry") {
|
|
||||||
self.onboardingWizard.reset()
|
|
||||||
Task {
|
|
||||||
await self.onboardingWizard.startIfNeeded(
|
|
||||||
mode: self.state.connectionMode,
|
mode: self.state.connectionMode,
|
||||||
workspace: self.workspacePath.isEmpty ? nil : self.workspacePath)
|
workspacePath: self.workspacePath)
|
||||||
}
|
|
||||||
}
|
|
||||||
.buttonStyle(.borderedProminent)
|
|
||||||
} else if self.onboardingWizard.isStarting {
|
|
||||||
HStack(spacing: 8) {
|
|
||||||
ProgressView()
|
|
||||||
Text("Starting wizard…")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
} else if let step = self.onboardingWizard.currentStep {
|
|
||||||
OnboardingWizardStepView(
|
|
||||||
step: step,
|
|
||||||
isSubmitting: self.onboardingWizard.isSubmitting)
|
|
||||||
{ value in
|
|
||||||
Task { await self.onboardingWizard.submit(step: step, value: value) }
|
|
||||||
}
|
|
||||||
.id(step.id)
|
|
||||||
} else if self.onboardingWizard.isComplete {
|
|
||||||
Text("Wizard complete. Continue to the next step.")
|
|
||||||
.font(.headline)
|
|
||||||
} else {
|
|
||||||
Text("Waiting for wizard…")
|
|
||||||
.foregroundStyle(.secondary)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
@@ -60,3 +28,66 @@ extension OnboardingView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private struct OnboardingWizardCardContent: View {
|
||||||
|
@Bindable var wizard: OnboardingWizardModel
|
||||||
|
let mode: AppState.ConnectionMode
|
||||||
|
let workspacePath: String
|
||||||
|
|
||||||
|
private enum CardState {
|
||||||
|
case error(String)
|
||||||
|
case starting
|
||||||
|
case step(WizardStep)
|
||||||
|
case complete
|
||||||
|
case waiting
|
||||||
|
}
|
||||||
|
|
||||||
|
private var state: CardState {
|
||||||
|
if let error = wizard.errorMessage { return .error(error) }
|
||||||
|
if wizard.isStarting { return .starting }
|
||||||
|
if let step = wizard.currentStep { return .step(step) }
|
||||||
|
if wizard.isComplete { return .complete }
|
||||||
|
return .waiting
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
switch state {
|
||||||
|
case .error(let error):
|
||||||
|
Text("Wizard error")
|
||||||
|
.font(.headline)
|
||||||
|
Text(error)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
Button("Retry") {
|
||||||
|
wizard.reset()
|
||||||
|
Task {
|
||||||
|
await wizard.startIfNeeded(
|
||||||
|
mode: mode,
|
||||||
|
workspace: workspacePath.isEmpty ? nil : workspacePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
case .starting:
|
||||||
|
HStack(spacing: 8) {
|
||||||
|
ProgressView()
|
||||||
|
Text("Starting wizard…")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
case .step(let step):
|
||||||
|
OnboardingWizardStepView(
|
||||||
|
step: step,
|
||||||
|
isSubmitting: wizard.isSubmitting)
|
||||||
|
{ value in
|
||||||
|
Task { await wizard.submit(step: step, value: value) }
|
||||||
|
}
|
||||||
|
.id(step.id)
|
||||||
|
case .complete:
|
||||||
|
Text("Wizard complete. Continue to the next step.")
|
||||||
|
.font(.headline)
|
||||||
|
case .waiting:
|
||||||
|
Text("Waiting for wizard…")
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,20 +196,18 @@ struct OnboardingWizardStepView: View {
|
|||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private var textField: some View {
|
private var textField: some View {
|
||||||
let isSensitive = step.sensitive == true
|
let isSensitive = step.sensitive == true
|
||||||
if isSensitive {
|
if isSensitive {
|
||||||
return AnyView(
|
|
||||||
SecureField(step.placeholder ?? "", text: $textValue)
|
SecureField(step.placeholder ?? "", text: $textValue)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(maxWidth: 360)
|
.frame(maxWidth: 360)
|
||||||
)
|
} else {
|
||||||
}
|
|
||||||
return AnyView(
|
|
||||||
TextField(step.placeholder ?? "", text: $textValue)
|
TextField(step.placeholder ?? "", text: $textValue)
|
||||||
.textFieldStyle(.roundedBorder)
|
.textFieldStyle(.roundedBorder)
|
||||||
.frame(maxWidth: 360)
|
.frame(maxWidth: 360)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var selectOptions: some View {
|
private var selectOptions: some View {
|
||||||
@@ -240,15 +238,7 @@ struct OnboardingWizardStepView: View {
|
|||||||
private var multiselectOptions: some View {
|
private var multiselectOptions: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
ForEach(optionItems) { item in
|
ForEach(optionItems) { item in
|
||||||
Toggle(isOn: Binding(get: {
|
Toggle(isOn: bindingForOption(item)) {
|
||||||
selectedIndices.contains(item.index)
|
|
||||||
}, set: { newValue in
|
|
||||||
if newValue {
|
|
||||||
selectedIndices.insert(item.index)
|
|
||||||
} else {
|
|
||||||
selectedIndices.remove(item.index)
|
|
||||||
}
|
|
||||||
})) {
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
Text(item.option.label)
|
Text(item.option.label)
|
||||||
if let hint = item.option.hint, !hint.isEmpty {
|
if let hint = item.option.hint, !hint.isEmpty {
|
||||||
@@ -262,6 +252,18 @@ struct OnboardingWizardStepView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func bindingForOption(_ item: WizardOptionItem) -> Binding<Bool> {
|
||||||
|
Binding(get: {
|
||||||
|
selectedIndices.contains(item.index)
|
||||||
|
}, set: { newValue in
|
||||||
|
if newValue {
|
||||||
|
selectedIndices.insert(item.index)
|
||||||
|
} else {
|
||||||
|
selectedIndices.remove(item.index)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private var isBlocked: Bool {
|
private var isBlocked: Bool {
|
||||||
let type = wizardStepType(step)
|
let type = wizardStepType(step)
|
||||||
if type == "select" { return optionItems.isEmpty }
|
if type == "select" { return optionItems.isEmpty }
|
||||||
|
|||||||
Reference in New Issue
Block a user