Files
ShadowsocksX-NG/ShadowsocksX-NG2/App/UI/Workspace/StatusCardView.swift
T
Qiu Yuzhou c708cd8d19 feat(ui): debounce transient status rows via shared display gate
The home run-control card and the menu-bar status card render the agent
and system-proxy status rows straight from snapshot replacements, so fast
operations (e.g. proxy mode switches) flash transient states like
"applying" before settling.

Add a presentation-layer debounce shared by both surfaces:

- TransientDelayGate: per-stream gate where stable values display
  immediately, transient values (starting/applying/repairing) display
  after 400 ms and are dropped when superseded, and displayed transients
  are held for at least 400 ms.
- StatusDisplayModel: app-level shared model deriving agent and
  system-proxy payloads (row + detail + hint) from workflow snapshots
  through two independent gates; domain facts keep their cadence.
- Wire the model through the composition root into RuntimeControlCard
  and StatusCardView; toggles, approval actions, the difference view and
  target/mode rows keep reading the raw snapshot.
2026-10-24 02:22:39 +08:00

49 lines
1.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import SwiftUI
/// 菜单栏面板代理状态卡。状态行经 StatusDisplayModel 的瞬态门控呈现
/// (与首页运行控制卡同一 debounce 口径);活动目标与模式行仍从 control
/// 的整体 snapshot 直接派生。
struct StatusCardView: View {
@ObservedObject var control: ProxyControlWorkflow
let statusDisplay: StatusDisplayModel
var body: some View {
let card = StatusCardModel.card(from: control.snapshot)
row(statusDisplay.agent.row)
if let detail = statusDisplay.agent.detail {
detailLine(detail)
}
row(statusDisplay.systemProxy.row)
if let detail = statusDisplay.systemProxy.detail {
detailLine(detail)
}
Text(card.target.text)
.truncationMode(.middle)
.help(card.target.help)
.accessibilityElement(children: .ignore)
.accessibilityLabel("活动目标:\(card.target.text)")
Text("模式:\(card.modeText)").accessibilityElement(children: .combine)
}
private func row(_ row: StatusCardModel.Row) -> some View {
Text("\(row.label):\(row.text)").accessibilityElement(children: .combine)
}
private func detailLine(_ detail: StatusCardModel.DetailLine) -> some View {
Text(detail.text)
.lineLimit(2)
.help(detail.text)
}
private func color(_ tone: StatusCardModel.Tone) -> Color {
switch tone {
case .positive: .green
case .attention: .orange
case .failure: .red
case .neutral: .secondary
}
}
}