mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-11-14 18:23: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 阶段。
121 lines
3.6 KiB
Swift
121 lines
3.6 KiB
Swift
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)
|
||
}
|