Files
Termind/TFM/Design/StatusIndicator.swift
T

61 lines
2.2 KiB
Swift

import SwiftUI
import TermindDomain
/// The shared visual vocabulary for connection state.
///
/// Sidebar rows, workspace items, and the status bar all render the same dot, so a
/// color means one thing everywhere in the window.
extension ConnectionStatus {
var tint: Color {
switch self {
case .disconnected: .secondary
case .connecting, .reconnecting: TermindColor.warning
case .connected: TermindColor.success
case .failed: TermindColor.danger
}
}
var label: String {
switch self {
case .disconnected: String(localized: "Not connected")
case .connecting: String(localized: "Connecting")
case .connected: String(localized: "Connected")
case .reconnecting: String(localized: "Reconnecting")
case .failed(let message): message
}
}
}
/// A connection-state dot. Pulses while a connection is being established, and
/// turns blue when a background workspace has produced output the user has not seen.
struct StatusDot: View {
let status: ConnectionStatus
var hasUnread: Bool = false
var size: CGFloat = Metrics.Sidebar.statusDot
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var isPulsing = false
var body: some View {
let animatesPulse = isPulsing && !reduceMotion
Circle()
.fill(fill)
.frame(width: size, height: size)
.opacity(animatesPulse ? 0.3 : 1)
// A repeating animation must disappear when the status settles.
// Reusing it for the transition back to `false` leaves SwiftUI's
// display link running even though the dot looks stationary.
.animation(animatesPulse ? Motion.statusPulse : nil, value: animatesPulse)
.onAppear { isPulsing = status.isBusy }
.onChange(of: status.isBusy) { _, busy in isPulsing = busy }
.accessibilityLabel(Text(status.label))
}
/// Unread output only shows once the connection has settled — while
/// connecting, the connection state is the more useful thing to signal.
private var fill: Color {
hasUnread && !status.isBusy ? TermindColor.accent : status.tint
}
}