mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 10:13:54 +00:00
App/ 根目录原有 73 个散落源文件,已无法从目录读出任何分组信息。 按「目录即分层」重组为 Composition / Application / Presentation / UI / PlatformEffects / Resources 六个目录,UI 层再按子视图分目录(Workspace、 Home、Servers(含 Plugin)、Subscriptions、Rules、Settings、Diagnostics、 StatusMenu)。纯移动:类型名、模块名与 import 均未改动,App/ 根层不再有 散落文件。 配套改动: - project.yml:Core 测试镜像的 excludes 由裸文件名改为 `**/` 递归形式。 XcodeGen 的 exclude 不跨 `/`,裸名只匹配 App 根层;MainApp.swift 下移后 旧写法会静默失效,把 @main 组合根编进测试镜像。info.path 与 entitlements.path 指向 App/Resources/;并排除 `**/*.md`——App/ 下任何 非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle。 - SkeletonTests 的架构守卫由「默认扫描全部 App/ + 7 个豁免文件 + 1 个豁免 前缀 + 4 个豁免目录」改为正面圈定 App/UI/,豁免登记全部删除;新增运行时 适配器放进 Application/ 即自动落在扫描范围外,无需再登记。 - README 工程结构说明同步;根 .gitignore 的生成物路径随 Resources/ 归位。 验证:clean build 0 error;task test 1022 用例全通过(含两个架构守卫); task format 无改动(证明纯移动);task lint 与移动前同为 21 条内容型 warning;packaging-gate.sh ALL CHECKS PASSED,确认 plist/entitlements 归位 后签名与 entitlements 语义不变。 注意:迁移时须先删旧的生成物再 xcodegen generate,否则遗留的 App/Info.plist 会被当作普通资源扫进 Resources 阶段。
80 lines
2.3 KiB
Swift
80 lines
2.3 KiB
Swift
import SwiftUI
|
||
|
||
/// 侧栏底部代理状态卡(issue #60):只做 tone→语义色映射与排版,行结构、
|
||
/// 回退文案与色调政策全部来自 StatusCardModel(与状态菜单同用一份文本映射,
|
||
/// issue #47 的整体 snapshot 口径)。
|
||
struct StatusCardView: View {
|
||
@ObservedObject var control: ProxyControlWorkflow
|
||
|
||
var body: some View {
|
||
let card = StatusCardModel.card(from: control.snapshot)
|
||
return VStack(alignment: .leading, spacing: 7) {
|
||
row(card.runtimeRow)
|
||
if let detail = card.runtimeDetail {
|
||
detailLine(detail)
|
||
}
|
||
row(card.systemProxyRow)
|
||
if let detail = card.systemProxyDetail {
|
||
detailLine(detail)
|
||
}
|
||
|
||
Text(card.target.text)
|
||
.font(.footnote.weight(.medium))
|
||
.lineLimit(1)
|
||
.truncationMode(.middle)
|
||
.help(card.target.help)
|
||
.accessibilityElement(children: .ignore)
|
||
.accessibilityLabel("活动目标:\(card.target.text)")
|
||
|
||
HStack(spacing: 0) {
|
||
Text("模式:")
|
||
.foregroundStyle(.secondary)
|
||
Text(card.modeText)
|
||
}
|
||
.font(.caption)
|
||
.accessibilityElement(children: .combine)
|
||
}
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.padding(12)
|
||
.background(.background.secondary, in: RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||
.overlay(
|
||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||
.strokeBorder(.quaternary)
|
||
)
|
||
.padding(.horizontal, 12)
|
||
.padding(.top, 8)
|
||
.padding(.bottom, 12)
|
||
}
|
||
|
||
private func row(_ row: StatusCardModel.Row) -> some View {
|
||
HStack(spacing: 4) {
|
||
Text("\(row.label):")
|
||
.foregroundStyle(.secondary)
|
||
Text(row.text)
|
||
.fontWeight(.semibold)
|
||
.foregroundStyle(color(row.tone))
|
||
}
|
||
.font(.footnote)
|
||
.lineLimit(1)
|
||
.accessibilityElement(children: .combine)
|
||
}
|
||
|
||
private func detailLine(_ detail: StatusCardModel.DetailLine) -> some View {
|
||
Text(detail.text)
|
||
.font(.caption)
|
||
.foregroundStyle(color(detail.tone))
|
||
.lineLimit(2)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
.help(detail.text)
|
||
}
|
||
|
||
private func color(_ tone: StatusCardModel.Tone) -> Color {
|
||
switch tone {
|
||
case .positive: .green
|
||
case .attention: .orange
|
||
case .failure: .red
|
||
case .neutral: .secondary
|
||
}
|
||
}
|
||
}
|