mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-08 11:54:50 +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 阶段。
106 lines
3.5 KiB
Swift
106 lines
3.5 KiB
Swift
import SwiftUI
|
|
|
|
/// A reserved toolbar row keeps feedback from resizing or replacing the rule table.
|
|
struct RulesOperationStatusView: View {
|
|
@ObservedObject var workflow: RulesWorkflow
|
|
@State private var showsDetails = false
|
|
|
|
var body: some View {
|
|
HStack(spacing: 8) {
|
|
Spacer(minLength: 0)
|
|
switch workflow.snapshot.operationStatus {
|
|
case .idle, .initialLoading:
|
|
EmptyView()
|
|
case .updating:
|
|
progress("正在更新规则…")
|
|
case .refreshing:
|
|
progress("正在加载规则…")
|
|
case .collectionIncomplete:
|
|
Label(RulesCopy.text("集合不完整"), systemImage: "exclamationmark.triangle")
|
|
.foregroundStyle(.orange)
|
|
detailsButton
|
|
refreshButton
|
|
case .feedback(let feedback):
|
|
Label {
|
|
Text(verbatim: feedback.summary).lineLimit(1).truncationMode(.middle)
|
|
} icon: {
|
|
Image(
|
|
systemName: feedback.outcome.isSuccess ? "checkmark.circle" : "exclamationmark.triangle"
|
|
)
|
|
.foregroundStyle(feedback.outcome.isSuccess ? Color.secondary : Color.orange)
|
|
}
|
|
.help(feedback.summary)
|
|
if !feedback.outcome.isSuccess {
|
|
detailsButton
|
|
Button(RulesCopy.text("关闭"), systemImage: "xmark") {
|
|
workflow.dismissFeedback()
|
|
}.labelStyle(.iconOnly)
|
|
}
|
|
}
|
|
}
|
|
.font(.caption)
|
|
.controlSize(.small)
|
|
.frame(maxWidth: .infinity, minHeight: 22, maxHeight: 22)
|
|
.onChange(of: workflow.snapshot.operationStatus) { _, _ in showsDetails = false }
|
|
}
|
|
|
|
private func progress(_ key: String) -> some View {
|
|
HStack(spacing: 6) {
|
|
ProgressView().controlSize(.mini)
|
|
Text(RulesCopy.text(key))
|
|
}.accessibilityElement(children: .combine)
|
|
}
|
|
|
|
private var detailsButton: some View {
|
|
Button(RulesCopy.text("查看详情")) { showsDetails = true }
|
|
.popover(isPresented: $showsDetails) { details }
|
|
}
|
|
|
|
private var refreshButton: some View {
|
|
Button(RulesCopy.text("刷新规则")) {
|
|
showsDetails = false
|
|
Task { await workflow.refresh(retryFailedSources: true) }
|
|
}
|
|
}
|
|
|
|
private var details: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: 10) {
|
|
if let feedback = workflow.snapshot.commitFeedback {
|
|
Text(verbatim: feedback.summary).font(.headline)
|
|
if let detail = feedback.outcome.failureDetail {
|
|
Text(verbatim: detail).textSelection(.enabled)
|
|
}
|
|
if let nextStep = feedback.outcome.nextStep {
|
|
Text(RulesCopy.text(nextStep))
|
|
}
|
|
}
|
|
if !workflow.snapshot.issues.isEmpty {
|
|
Text(RulesCopy.text("规则列表未更新,请刷新规则。"))
|
|
RulesCollectionIssuesView(issues: workflow.snapshot.issues)
|
|
}
|
|
}.frame(maxWidth: .infinity, alignment: .leading).padding()
|
|
}.frame(width: 380).frame(maxHeight: 300)
|
|
}
|
|
}
|
|
|
|
struct RulesCollectionIssuesView: View {
|
|
let issues: [RulesPageSnapshot.Issue]
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 6) {
|
|
Text(RulesCopy.text("集合不完整时,覆盖解释仅基于已加载来源。"))
|
|
ForEach(issues, id: \.self) { issue in
|
|
Text(verbatim: description(issue)).textSelection(.enabled)
|
|
}
|
|
}.font(.caption)
|
|
}
|
|
|
|
private func description(_ issue: RulesPageSnapshot.Issue) -> String {
|
|
switch issue {
|
|
case .userDocument(let detail): "\(RulesCopy.text("自定义规则")): \(detail)"
|
|
case .builtin(let source, let detail): "\(source.label): \(detail)"
|
|
}
|
|
}
|
|
}
|