mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 22:44:50 +00:00
109 lines
4.1 KiB
Swift
109 lines
4.1 KiB
Swift
import Foundation
|
|
|
|
/// 菜单栏面板代理状态卡的呈现模型:从代理控制
|
|
/// 工作流的整体 snapshot 派生卡片数据——行结构(哪些行、条件 detail、顺序)、
|
|
/// 活动目标回退文案与语义色调政策全部在此决定;文本复用 StatusMenuModel 的
|
|
/// 同一映射(配色与文本同源)。不持有 SwiftUI/AppKit 类型,纯逻辑可测;视觉
|
|
/// 细节(tone→语义色、排版)在 StatusCardView 呈现,不建自动测试。
|
|
enum StatusCardModel {
|
|
/// 语义色调:呈现层映射为系统语义色(positive=green、attention=orange、
|
|
/// failure=red、neutral=secondary)。
|
|
enum Tone: Equatable {
|
|
case positive
|
|
case attention
|
|
case failure
|
|
case neutral
|
|
}
|
|
|
|
struct Row: Equatable {
|
|
let label: String
|
|
let text: String
|
|
let tone: Tone
|
|
}
|
|
|
|
struct DetailLine: Equatable {
|
|
let text: String
|
|
let tone: Tone
|
|
}
|
|
|
|
/// 活动目标行:正文与悬停/辅助说明(未激活时按模式区分回退文案)。
|
|
struct TargetPresentation: Equatable {
|
|
let text: String
|
|
let help: String
|
|
}
|
|
|
|
struct Card: Equatable {
|
|
let runtimeRow: Row
|
|
/// agent 侧点名原因(运行失败或激活拒绝);无则 nil。
|
|
let runtimeDetail: DetailLine?
|
|
let systemProxyRow: Row
|
|
/// 系统代理写入/清理失败点名;非失败态为 nil。
|
|
let systemProxyDetail: DetailLine?
|
|
let target: TargetPresentation
|
|
/// 模式行文本(规则模式带子选项,与状态菜单同口径)。
|
|
let modeText: String
|
|
}
|
|
|
|
static func card(from snapshot: ProxyControlSnapshot) -> Card {
|
|
let summary = StatusMenuModel.summary(from: snapshot)
|
|
let runtimeTone = runtimeTone(for: snapshot.runtime.status)
|
|
return Card(
|
|
runtimeRow: Row(label: String(localized: "后台代理"), text: summary.status, tone: runtimeTone),
|
|
runtimeDetail: summary.detail.map {
|
|
DetailLine(text: $0, tone: runtimeDetailTone(snapshot: snapshot, runtimeTone: runtimeTone))
|
|
},
|
|
systemProxyRow: Row(
|
|
label: String(localized: "系统代理设置"),
|
|
text: summary.systemProxyStateLabel,
|
|
tone: systemProxyTone(for: snapshot.systemProxyApplication)),
|
|
systemProxyDetail: summary.systemProxyDetail.map {
|
|
DetailLine(text: $0, tone: .failure)
|
|
},
|
|
target: targetPresentation(summary: summary, proxyMode: snapshot.proxyMode),
|
|
modeText: summary.modeLabel)
|
|
}
|
|
|
|
private static func runtimeTone(for status: ProxyRuntimeStatus) -> Tone {
|
|
switch status {
|
|
case .running: .positive
|
|
case .starting, .off: .neutral
|
|
case .firewallBlocked, .requiresApproval: .attention
|
|
case .launchFailed, .serviceFailed: .failure
|
|
}
|
|
}
|
|
|
|
/// agent 点名原因色调:运行失败跟随运行状态色调;激活点名失败独立显红;
|
|
/// 其余中性。
|
|
private static func runtimeDetailTone(snapshot: ProxyControlSnapshot, runtimeTone: Tone) -> Tone {
|
|
if snapshot.runtime.failure != nil {
|
|
return runtimeTone
|
|
}
|
|
return snapshot.activationFailure == nil ? .neutral : .failure
|
|
}
|
|
|
|
private static func systemProxyTone(for application: SystemProxyApplicationFacts) -> Tone {
|
|
switch application {
|
|
case .idle: .neutral
|
|
case .pending, .changed, .applying, .repairing, .paused: .attention
|
|
case .applied: .positive
|
|
case .failed, .repairFailed, .clearFailed, .unreadable: .failure
|
|
}
|
|
}
|
|
|
|
/// 活动目标回退:有目标用路径;未激活时直连模式无需目标,其余模式提示
|
|
/// 未激活。
|
|
private static func targetPresentation(
|
|
summary: StatusMenuModel.Summary, proxyMode: ProxyMode
|
|
) -> TargetPresentation {
|
|
if let targetPath = summary.targetPath {
|
|
return TargetPresentation(text: targetPath, help: String(localized: "活动目标:\(targetPath)"))
|
|
}
|
|
if proxyMode == .direct {
|
|
return TargetPresentation(
|
|
text: String(localized: "直连模式(无需服务器)"), help: String(localized: "直连模式无需选择活动目标"))
|
|
}
|
|
return TargetPresentation(
|
|
text: String(localized: "未激活"), help: String(localized: "未设置活动目标;在首页或服务器目录中激活"))
|
|
}
|
|
}
|