import SwiftUI struct LanStepView: View { @ObservedObject var viewModel: SetupViewModel let availableInterfaces: [NetworkInterface] @AppStorage("appLanguage") private var appLanguage: String = "de" var body: some View { Form { ForEach($viewModel.lanConfigs) { $config in Section(L10n.t("Netzwerk auf", appLanguage) + " \(config.interfaceName)") { Picker(L10n.t("Anschluss/Bridge", appLanguage), selection: $config.interfaceName) { ForEach(availableInterfaces) { interface in Text(interface.name).tag(interface.name) } } .help(L10n.t("Der interne Netzwerk-Anschluss, an dem deine Geräte hängen (dein lokales Netzwerk, LAN).", appLanguage)) .onChange(of: config.interfaceName) { _, _ in viewModel.checkPortConflict(for: config.id) } .onAppear { viewModel.checkPortConflict(for: config.id) } if viewModel.isCheckingPortConflict.contains(config.id) { HStack { ProgressView().controlSize(.small) Text(L10n.t("Prüfe, ob der Port frei ist…", appLanguage)).appFont(.caption).foregroundStyle(.secondary) } } else if let conflict = viewModel.lanPortConflicts[config.id] { PortConflictWarningView( conflict: conflict, isAcknowledged: viewModel.acknowledgedPortConflicts.contains(config.id), appLanguage: appLanguage, immediateApply: false, onConfirm: { viewModel.acknowledgePortConflict(for: config.id) } ) } TextField(L10n.t("Router-Adresse (z.B. 192.168.88.1/24)", appLanguage), text: $config.routerAddress) .help(L10n.t("Die Adresse, unter der der Router selbst in diesem Netzwerk erreichbar ist.", appLanguage)) TextField(L10n.t("Netzwerk (z.B. 192.168.88.0/24)", appLanguage), text: $config.networkAddress) .help(L10n.t("Der komplette Adressbereich dieses Netzwerks (z.B. /24 erlaubt bis zu 254 Geräte).", appLanguage)) TextField(L10n.t("Automatische Adressvergabe von", appLanguage), text: $config.poolRangeStart) .help(L10n.t("Ab welcher Adresse der Router automatisch Adressen an Geräte in diesem Netzwerk vergibt.", appLanguage)) TextField(L10n.t("bis", appLanguage), text: $config.poolRangeEnd) .help(L10n.t("Bis zu welcher Adresse der Router automatisch Adressen an Geräte in diesem Netzwerk vergibt.", appLanguage)) Stepper( L10n.t("Adresse behalten für", appLanguage) + " \(config.leaseTimeHours) " + L10n.t("Stunden", appLanguage), value: $config.leaseTimeHours, in: 1...168 ) .help(L10n.t("Wie lange ein Gerät seine zugewiesene Adresse behält, bevor sie erneuert werden muss.", appLanguage)) TextField(L10n.t("DNS-Server", appLanguage), text: $config.dnsServers) .help(L10n.t("Welcher Server Geräten in diesem Netzwerk Internetadressen in Namen übersetzt (z.B. www.google.de). Meist der Router selbst.", appLanguage)) if viewModel.mode == .expert { Toggle(L10n.t("Von anderen Netzwerken isolieren", appLanguage), isOn: $config.isolated) .help(L10n.t("Verhindert Datenverkehr zwischen diesem und allen anderen konfigurierten LAN-/VLAN-Netzwerken. Der Internetzugriff bleibt erhalten. Wird im Firewall-Schritt umgesetzt.", appLanguage)) if viewModel.lanConfigs.count > 1 { Button(L10n.t("Netzwerk entfernen", appLanguage), role: .destructive) { viewModel.removeLan(config.id) } } } } } if viewModel.mode == .expert { Section { Button(L10n.t("Weiteres LAN-Netzwerk hinzufügen", appLanguage)) { viewModel.addLan() } } } Section { HStack { Button(L10n.t("Zurück", appLanguage)) { viewModel.goBack() } Spacer() Button(L10n.t("Weiter", appLanguage)) { viewModel.goNext() } .disabled(!isStepValid) } } } .formStyle(.grouped) .navigationTitle(LocalizedStringKey(L10n.t("Heimnetzwerk einrichten", appLanguage))) .toolbar { ToolbarItem { ManualHelpButton(anchor: ManualAnchor.stepLAN) } } } private var isStepValid: Bool { viewModel.lanConfigs.allSatisfy { config in !config.interfaceName.isEmpty && !config.routerAddress.isEmpty && !config.networkAddress.isEmpty && !config.poolRangeStart.isEmpty && !config.poolRangeEnd.isEmpty && !viewModel.hasUnresolvedPortConflict(for: config.id) } } } // `PortConflictWarningView` now lives in Features/Shared/ — shared with the Experte tab's // equivalent flow (see that file's doc comment for why).