mirror of
https://github.com/home-assistant/iOS.git
synced 2026-02-04 11:42:39 -06:00
<!-- Thank you for submitting a Pull Request and helping to improve Home Assistant. Please complete the following sections to help the processing and review of your changes. Please do not delete anything from this template. --> ## Summary <!-- Provide a brief summary of the changes you have made and most importantly what they aim to achieve --> ## Screenshots <!-- If this is a user-facing change not in the frontend, please include screenshots in light and dark mode. --> <img width="2360" height="1640" alt="Simulator Screenshot - iPad Air 11-inch (M3) - 2026-01-21 at 13 27 07" src="https://github.com/user-attachments/assets/1f7aac7c-2b1b-4252-b81d-e2ca27c40700" /> ## Link to pull request in Documentation repository <!-- Pull requests that add, change or remove functionality must have a corresponding pull request in the Companion App Documentation repository (https://github.com/home-assistant/companion.home-assistant). Please add the number of this pull request after the "#" --> Documentation: home-assistant/companion.home-assistant# ## Any other notes <!-- If there is any other information of note, like if this Pull Request is part of a bigger change, please include it here. --> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
156 lines
5.0 KiB
Swift
156 lines
5.0 KiB
Swift
import SFSafeSymbols
|
|
import Shared
|
|
import SwiftUI
|
|
|
|
struct ConnectivityCheckView: View {
|
|
@ObservedObject var state: ConnectivityCheckState
|
|
let url: URL
|
|
let onRunChecks: () -> Void
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.two) {
|
|
HStack {
|
|
Text(L10n.Connectivity.Diagnostics.title)
|
|
.font(.headline.bold())
|
|
Spacer()
|
|
if !state.isRunning {
|
|
Button(action: onRunChecks) {
|
|
HStack(spacing: 4) {
|
|
Image(systemSymbol: .arrowClockwise)
|
|
.font(.caption)
|
|
Text(L10n.Connectivity.Diagnostics.runChecks)
|
|
.font(.caption.bold())
|
|
}
|
|
.foregroundStyle(.blue)
|
|
}
|
|
}
|
|
}
|
|
|
|
if state.checks.allSatisfy({ $0.result == .pending }) {
|
|
Button(action: onRunChecks) {
|
|
Label(L10n.Connectivity.Diagnostics.start, systemSymbol: .stethoscope)
|
|
}
|
|
.buttonStyle(.primaryButton)
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
} else {
|
|
VStack(alignment: .leading, spacing: DesignSystem.Spaces.oneAndHalf) {
|
|
ForEach(state.checks) { check in
|
|
ConnectivityCheckRow(check: check)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(.vertical)
|
|
}
|
|
}
|
|
|
|
struct ConnectivityCheckRow: View {
|
|
let check: ConnectivityCheck
|
|
|
|
var body: some View {
|
|
HStack(spacing: DesignSystem.Spaces.one) {
|
|
statusIcon
|
|
.frame(width: 24, height: 24)
|
|
|
|
VStack(alignment: .leading, spacing: 2) {
|
|
Text(check.type.localizedName)
|
|
.font(.body.bold())
|
|
|
|
if case let .success(message) = check.result, let message {
|
|
Text(message)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
} else if case let .failure(error) = check.result {
|
|
Text(error)
|
|
.font(.caption)
|
|
.foregroundStyle(.red)
|
|
} else if check.result == .skipped {
|
|
Text(L10n.Connectivity.Check.skipped)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
} else if check.result == .running {
|
|
Text(L10n.Connectivity.Check.running)
|
|
.font(.caption)
|
|
.foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.vertical, 4)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var statusIcon: some View {
|
|
switch check.result {
|
|
case .pending:
|
|
Image(systemSymbol: .circle)
|
|
.foregroundStyle(.gray)
|
|
case .running:
|
|
ProgressView()
|
|
case .success:
|
|
Image(systemSymbol: .checkmarkCircleFill)
|
|
.foregroundStyle(.green)
|
|
case .failure:
|
|
Image(systemSymbol: .xmarkCircleFill)
|
|
.foregroundStyle(.red)
|
|
case .skipped:
|
|
Image(systemSymbol: .minusCircleFill)
|
|
.foregroundStyle(.gray)
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview("Pending") {
|
|
ConnectivityCheckView(
|
|
state: ConnectivityCheckState(),
|
|
url: URL(string: "https://example.com")!,
|
|
onRunChecks: {}
|
|
)
|
|
.padding()
|
|
}
|
|
|
|
#Preview("Running") {
|
|
let state = ConnectivityCheckState()
|
|
state.updateCheck(type: .dns, result: .success(message: "Resolved to 93.184.216.34"))
|
|
state.updateCheck(type: .port, result: .running)
|
|
state.isRunning = true
|
|
|
|
return ConnectivityCheckView(
|
|
state: state,
|
|
url: URL(string: "https://example.com")!,
|
|
onRunChecks: {}
|
|
)
|
|
.padding()
|
|
}
|
|
|
|
#Preview("Success") {
|
|
let state = ConnectivityCheckState()
|
|
state.updateCheck(type: .dns, result: .success(message: "Resolved to 93.184.216.34"))
|
|
state.updateCheck(type: .port, result: .success(message: "Port 443 is reachable"))
|
|
state.updateCheck(type: .tls, result: .success(message: "Certificate is valid"))
|
|
state.updateCheck(type: .server, result: .success(message: "Server responded with status 200"))
|
|
|
|
return ConnectivityCheckView(
|
|
state: state,
|
|
url: URL(string: "https://example.com")!,
|
|
onRunChecks: {}
|
|
)
|
|
.padding()
|
|
}
|
|
|
|
#Preview("Failure") {
|
|
let state = ConnectivityCheckState()
|
|
state.updateCheck(type: .dns, result: .success(message: "Resolved to 93.184.216.34"))
|
|
state.updateCheck(type: .port, result: .failure(error: "Connection timeout"))
|
|
state.updateCheck(type: .tls, result: .skipped)
|
|
state.updateCheck(type: .server, result: .skipped)
|
|
|
|
return ConnectivityCheckView(
|
|
state: state,
|
|
url: URL(string: "https://example.com")!,
|
|
onRunChecks: {}
|
|
)
|
|
.padding()
|
|
}
|