Files
ShadowsocksX-NG/ShadowsocksX-NG2/App/Application/SettingsWorkflow/SettingsWorkflowModels.swift
T
Qiu Yuzhou fb428f12bc refactor(app): 按分层重组 App/ 源码目录
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 阶段。
2026-10-05 19:56:18 +08:00

121 lines
3.6 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 Foundation
// MARK: - Setting editor models
/// Stable UI identifier for the SOCKS5 and HTTP port fields. The view does not
/// need to import the probe layer's endpoint type.
enum SettingsPortID: CaseIterable, Hashable, Sendable {
case socks
case http
}
/// The SOCKS5 and HTTP ports edited and saved together as one settings item.
struct SettingsPortDraft: Equatable, Sendable {
var socksPort: Int
var httpPort: Int
func portValue(for id: SettingsPortID) -> Int {
switch id {
case .socks: socksPort
case .http: httpPort
}
}
mutating func setPortValue(_ value: Int, for id: SettingsPortID) {
switch id {
case .socks: socksPort = value
case .http: httpPort = value
}
}
}
// MARK: - 按字段归位的校验问题
/// 字段标识(按字段归位查询);端口问题用端口标识变体。
enum SettingsFieldID: Hashable, Sendable {
case port(SettingsPortID)
}
/// 按字段归位的校验问题:每条保留 Domain 的 typed fact;文案由 App
/// presentation edge 派生。
enum SettingsFieldIssue: Hashable, Sendable {
case port(SettingsPortID, error: ProxySettingsValidationError)
var field: SettingsFieldID {
switch self {
case .port(let id, _): .port(id)
}
}
var error: ProxySettingsValidationError {
switch self {
case .port(_, let error):
error
}
}
}
// MARK: - 端口 field state
/// 端口占用的类型化事实(UI 形状):文案与颜色由视图决定。
enum SettingsPortOccupancy: Equatable, Sendable {
case free
/// 端口不可绑定;占用进程名尽力解析(未知为 nil)。
case occupied(occupier: String?)
case unknown(detail: String)
}
/// 每个端口字段的 field state:两个端口行复用同一套呈现逻辑。
struct SettingsPortFieldState: Equatable, Sendable {
let id: SettingsPortID
/// 该端口当前草稿值。
let draftValue: Int
/// 类型化占用事实;`nil` = 尚未探测。
let occupancy: SettingsPortOccupancy?
/// 该端口的字段问题事实;文案由 presentation edge 派生。
let issues: [SettingsFieldIssue]
/// Whether the running listener matches the complete edited listen identity.
let isRuntimePortException: Bool
/// 是否可建议空闲端口(被占用且不是运行中端口例外)。
let canSuggestFreePort: Bool
}
// MARK: - 命令结果与拒绝原因
/// Typed reasons why a command cannot proceed. The workflow exposes facts,
/// not localized strings or a Boolean that makes validation and occupancy
/// failures indistinguishable.
enum SettingsCommandRejection: Equatable, Sendable {
case inProgress
case superseded
case validation([SettingsFieldIssue])
case occupied([SettingsPortID])
case noFreePort(SettingsPortID)
}
enum SettingsPersistenceFailure: Error, Equatable, Sendable {
case store(ProxySettingsStoreError)
case unknown
}
/// Results for port suggestion and item-save commands. Proxy runtime status is
/// presented by its owning status UI, not this settings interface.
enum SettingsCommandOutcome: Equatable, Sendable {
case rejected(SettingsCommandRejection)
case suggestedPort(port: SettingsPortID, value: Int)
case persisted
case persistenceFailed(SettingsPersistenceFailure)
}
/// Settings workflow 的最后失败仍是 typed;workflow 不提前渲染句子。
enum SettingsWorkflowFailure: Error, Equatable, Sendable {
case store(ProxySettingsStoreError)
case unknown
}
enum ListenerModeSaveOutcome: Equatable, Sendable {
case saved(unknownOccupancy: [SettingsPortID])
case rejected(SettingsCommandRejection)
case persistenceFailed(SettingsPersistenceFailure)
}