import SwiftUI /// Shown inline wherever a user is about to configure a physical port that /// `ConnectionService.checkPortConflict(interfaceName:)` found already carrying other /// configuration — originally the Setup Wizard's LAN step only (found.md #1's "Port-Konflikt- /// Prüfung"), now also the Experte tab's generic `.interfacePick` fields (per explicit request: /// "die Abfrage vom Einrichten-Assistenten auf Expert anwenden ... mit allen Warnungen"). /// Requires two separate confirmations before the app is allowed to clear it — per explicit /// request: this silently overriding a port's existing role (an active WAN dial-up, a bridge /// membership, a manually-set address) previously wasn't visible to the user at all beyond /// `DhcpServerCommandBuilder`'s own unconditional bridge detach; this makes the consequences /// explicit and opt-in instead. struct PortConflictWarningView: View { let conflict: PortConflict let isAcknowledged: Bool let appLanguage: String /// Wizard: resolution runs later, batched with everything else at "Jetzt anwenden" — the /// final confirmation dialog says so, and that a port switch above still undoes it. Experte /// tab: there's no separate review/apply step, "Anlegen"/"Speichern" runs it immediately — /// a different final-confirmation message reflects that instead. let immediateApply: Bool let onConfirm: () -> Void @State private var showConsequencesConfirmation = false @State private var showFinalConfirmation = false var body: some View { if isAcknowledged { Label("\(conflict.interfaceName) " + L10n.t("wird beim Anwenden freigemacht", appLanguage), systemImage: "checkmark.shield") .appFont(.caption) .foregroundStyle(.orange) } else { VStack(alignment: .leading, spacing: 6) { Label(L10n.t("Port", appLanguage) + " \(conflict.interfaceName) " + L10n.t("ist nicht frei", appLanguage), systemImage: "exclamationmark.triangle.fill") .appFont(.subheadline, bold: true) .foregroundStyle(.red) ForEach(Array(conflict.reasons.enumerated()), id: \.offset) { _, reason in Text("• \(reason.description)") .appFont(.caption) } Text(L10n.t("Wähle oben einen anderen, freien Port — oder mache diesen jetzt frei. Die bestehende Konfiguration wird dabei entfernt.", appLanguage)) .appFont(.caption) .foregroundStyle(.secondary) Button(L10n.t("Port jetzt freimachen…", appLanguage), role: .destructive) { showConsequencesConfirmation = true } } .padding(10) .background(RoundedRectangle(cornerRadius: 8).fill(Color.red.opacity(0.08))) .confirmationDialog( L10n.t("Port", appLanguage) + " \(conflict.interfaceName) " + L10n.t("freimachen?", appLanguage), isPresented: $showConsequencesConfirmation, titleVisibility: .visible ) { Button(L10n.t("Fortfahren", appLanguage), role: .destructive) { showFinalConfirmation = true } Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {} } message: { Text(consequenceText) } .confirmationDialog( L10n.t("Wirklich sicher?", appLanguage), isPresented: $showFinalConfirmation, titleVisibility: .visible ) { Button(L10n.t("Ja, endgültig freimachen", appLanguage), role: .destructive) { onConfirm() } Button(L10n.t("Abbrechen", appLanguage), role: .cancel) {} } message: { Text(immediateApply ? L10n.t("Wird sofort ausgeführt, sobald du unten auf \"Anlegen\"/\"Speichern\" klickst.", appLanguage) : L10n.t("Tatsächlich ausgeführt wird das erst mit \"Jetzt anwenden\" am Ende des Assistenten — bis dahin kannst du das rückgängig machen, indem du hier oben einen anderen Port wählst.", appLanguage)) } } } private var consequenceText: String { ([L10n.t("Folgendes wird entfernt, bevor", appLanguage) + " \(conflict.interfaceName) " + L10n.t("als neues Netzwerk eingerichtet wird:", appLanguage)] + conflict.reasons.map { "• \($0.description)" } + [L10n.t("Bestehender Datenverkehr über diesen Port (z.B. eine laufende Internetverbindung oder Geräte im bisherigen Netz) wird dadurch unterbrochen.", appLanguage)]) .joined(separator: "\n") } }