mirror of
https://github.com/shadowsocks/ShadowsocksX-NG.git
synced 2026-10-30 14:13:50 +00:00
ui(app): show selected address details as caption lines for #72
收起状态按钮只显示当前选中 IP;接口名称(接口:…)与地址类型 (地址类型:…)以说明行展示在按钮下方,与菜单项的 title/subtitle 结构对应。模型移除不再被呈现层使用的 label 组合字段。
This commit is contained in:
@@ -215,17 +215,26 @@ struct TerminalProxyEnvironmentCard: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(control.snapshot.commandAddressPicker.selected.label)
|
||||
Text(control.snapshot.commandAddressPicker.selected.address)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.menuStyle(.borderedButton)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if let annotation = control.snapshot.commandAddressPicker.selected.annotation {
|
||||
Text("地址类型:\(annotation)")
|
||||
// 收起状态详情:接口名称与地址类型以说明行展示在按钮下方。
|
||||
HStack(spacing: 4) {
|
||||
Text("接口:\(control.snapshot.commandAddressPicker.selected.displayName)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if let annotation = control.snapshot.commandAddressPicker.selected.annotation {
|
||||
Text("·")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
Text("地址类型:\(annotation)")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
commandButton(for: .zshBash)
|
||||
commandButton(for: .fish)
|
||||
|
||||
@@ -4,9 +4,9 @@ import SystemConfiguration
|
||||
// MARK: - 命令地址投影(issue #72)
|
||||
|
||||
/// 首页命令地址候选(CONTEXT.md「Terminal command address」)。身份 = BSD
|
||||
/// 接口身份 + 规范化 IP;显示名与类型注记仅用于呈现,不参与身份。`Hashable`
|
||||
/// 供 SwiftUI Picker 标签匹配使用,选择校验一律走 `identity`。
|
||||
struct TerminalCommandAddress: Equatable, Hashable, Sendable {
|
||||
/// 接口身份 + 规范化 IP;显示名与类型注记仅用于呈现,不参与身份,由呈现层
|
||||
/// 自行组合(菜单项主行 IP、副标题「注记 - 名称」与收起状态说明行)。
|
||||
struct TerminalCommandAddress: Equatable, Sendable {
|
||||
/// BSD 接口名;回环固定为 lo0。
|
||||
let bsdName: String
|
||||
/// 规范化 IP 文本。
|
||||
@@ -28,10 +28,6 @@ struct TerminalCommandAddress: Equatable, Hashable, Sendable {
|
||||
var identity: TerminalCommandAddressIdentity {
|
||||
TerminalCommandAddressIdentity(bsdName: bsdName, address: address)
|
||||
}
|
||||
|
||||
/// 候选主行标签:`{IP} - {name}`;类型注记由呈现层作为菜单项副标题与
|
||||
/// 说明行单独渲染,不进主行。
|
||||
var label: String { "\(address) - \(displayName)" }
|
||||
}
|
||||
|
||||
/// 命令地址身份:BSD 接口名 + 规范化 IP。
|
||||
|
||||
@@ -103,7 +103,7 @@ final class ProxyCommandAddressWorkflowTests: XCTestCase {
|
||||
["127.0.0.1", "::1", "240e::17", "240e::8150"],
|
||||
"RFC 4941 隐私临时地址被过滤(会轮换);其余按地址稳定排序")
|
||||
let secured = picker.candidates.first { $0.address == "240e::8150" }
|
||||
XCTAssertEqual(secured?.label, "240e::8150 - Wi-Fi", "主行标签不含注记")
|
||||
XCTAssertEqual(secured?.displayName, "Wi-Fi")
|
||||
XCTAssertEqual(secured?.annotation, "autoconf secured", "注记作为独立呈现事实")
|
||||
XCTAssertEqual(
|
||||
picker.candidates.first { $0.address == "240e::17" }?.annotation,
|
||||
@@ -125,7 +125,7 @@ final class ProxyCommandAddressWorkflowTests: XCTestCase {
|
||||
XCTAssertEqual(picker.selected.address, "::1")
|
||||
}
|
||||
|
||||
func testLabelsUseLocalizedNameWithBSDNameFallback() {
|
||||
func testDisplayNamesUseLocalizedNameWithBSDNameFallback() {
|
||||
setListenerMode(.allIPv4Interfaces)
|
||||
interfaceFacts.interfaces = [
|
||||
FakeLocalInterfaceFacts.wifi(localizedName: "Wi-Fi", addresses: [.ipv4("192.168.1.10")]),
|
||||
@@ -134,9 +134,9 @@ final class ProxyCommandAddressWorkflowTests: XCTestCase {
|
||||
interfaceFacts.emitChange()
|
||||
|
||||
XCTAssertEqual(
|
||||
workflow.snapshot.commandAddressPicker.candidates.map(\.label),
|
||||
["127.0.0.1 - lo0", "10.0.0.5 - en1", "192.168.1.10 - Wi-Fi"],
|
||||
"标签为 {IP} - {name};名称缺失回退 BSD 名,回环显示 lo0")
|
||||
workflow.snapshot.commandAddressPicker.candidates.map(\.displayName),
|
||||
["lo0", "en1", "Wi-Fi"],
|
||||
"名称缺失回退 BSD 名,回环显示 lo0")
|
||||
}
|
||||
|
||||
func testSameInterfaceDuplicateIPMergesAcrossInterfacesKept() {
|
||||
|
||||
@@ -88,15 +88,15 @@ final class SystemInterfaceFactsProviderTests: XCTestCase {
|
||||
XCTAssertTrue(
|
||||
type == kSCNetworkInterfaceTypeIEEE80211 as String
|
||||
|| type == kSCNetworkInterfaceTypeEthernet as String,
|
||||
"候选 \(candidate.label) 必须来自允许类型接口(实际类型 \(type ?? "nil"))")
|
||||
"候选 \(candidate.address) 必须来自允许类型接口(实际类型 \(type ?? "nil"))")
|
||||
XCTAssertFalse(candidate.address.contains("%"), "候选地址已规范化")
|
||||
XCTAssertFalse(
|
||||
fact?.v6Flags?.contains(.temporary) ?? false,
|
||||
"临时地址(会轮换)不进候选:\(candidate.label)")
|
||||
"临时地址(会轮换)不进候选:\(candidate.address)")
|
||||
let knownAnnotations: Set<String?> = ["autoconf secured", "autoconf", "dynamic", nil]
|
||||
XCTAssertTrue(
|
||||
knownAnnotations.contains(candidate.annotation),
|
||||
"IPv6 候选注记只能来自 ifconfig 同源闭集:\(candidate.label)")
|
||||
"IPv6 候选注记只能来自 ifconfig 同源闭集:\(candidate.address)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
| 所有 IPv4 与 IPv6 接口 | IPv4、IPv6,包含两个回环地址 | 127.0.0.1 |
|
||||
| 仅所有 IPv6 接口 | IPv6 | ::1 |
|
||||
|
||||
下拉菜单项主行显示 `{IP}`,副标题行显示「注记 - 接口名称」(无注记时仅接口名称;菜单项经 adaptive-controls 机制把两个 `Text` 桥接为 `NSMenuItem` 的原生 title/subtitle,注记与 ifconfig 同源,如 `autoconf secured`、`dynamic`),收起状态显示当前选中项的 `{IP} - {name}`,其下另有类型说明行,便于识别地址稳定性。接口名称优先取 `SCNetworkInterfaceGetLocalizedDisplayName(interface)`;名称缺失或无法映射到 SystemConfiguration 接口时,回退到 BSD 接口名,但不会因此放宽接口类型过滤。回环项回退名称为 lo0。
|
||||
下拉菜单项主行显示 `{IP}`,副标题行显示「注记 - 接口名称」(无注记时仅接口名称;菜单项经 adaptive-controls 机制把两个 `Text` 桥接为 `NSMenuItem` 的原生 title/subtitle,注记与 ifconfig 同源,如 `autoconf secured`、`dynamic`)。收起状态只显示当前选中项的 `{IP}`,其下以说明行展示「接口:{接口名称}」与「地址类型:{注记}」(有注记时),便于识别地址稳定性。接口名称优先取 `SCNetworkInterfaceGetLocalizedDisplayName(interface)`;名称缺失或无法映射到 SystemConfiguration 接口时,回退到 BSD 接口名,但不会因此放宽接口类型过滤。回环项回退名称为 lo0。
|
||||
|
||||
回环项置顶,双栈时 127.0.0.1 在 ::1 前。其余按接口名称排序,同一接口先 IPv4 后 IPv6,再按地址稳定排序。同一接口的重复 IP 合并;不同接口即使 IP 相同也保留接口身份。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user