release(ios): 0.2.2 构建 8 中性化批量标签禁用态并去蓝化图标底色

- FleetTagBatchSheet:无勾选时标签胶囊不再走 Button 禁用态(系统会压成灰块,像加载
  失败),改为中性标签原样渲染;覆盖度文案留空,图标改 tag;新建标签输入与按钮置灰
  并补一句「勾选节点后即可新建标签并批量贴上」。
- 应用图标:底色与插槽从偏青绿的石墨(R>G=B,亮壁纸上读作蓝)改为严格等值中性灰
  20/19/10,金色跨度拉大(#FADD98 -> #C78D2F)去掉芥末黄塑料感;AppIcon.svg 同步。
- 版本号:marketing 0.2.2 不带括号,构建号 7 -> 8;文档里 0.2.x (N) 一律改写为
  「0.2.x 构建 N」,README 与现行说明同步。
This commit is contained in:
chick
2026-09-15 05:07:03 +08:00
parent 36ce791215
commit 9ad0c46088
11 changed files with 120 additions and 63 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
## iOS Native 应用
`apps/ios` 提供不依赖 8788 控制台的原生 iPhone 应用,当前版本 **0.2.2**,构建号 7,运行在 iOS 16 及以上,产物为未签名 IPA,供 TrollStore 或自有签名流程安装。iOS 开发在 `ios-native-ipa` 分支进行。
`apps/ios` 提供不依赖 8788 控制台的原生 iPhone 应用,当前版本 **0.2.2**,构建号 8,运行在 iOS 16 及以上,产物为未签名 IPA,供 TrollStore 或自有签名流程安装。iOS 开发在 `ios-native-ipa` 分支进行。
底部为节点、消息、设置三个入口:
+8 -8
View File
@@ -2,18 +2,18 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="field" x1="0.06" y1="0" x2="0.94" y2="1">
<stop offset="0" stop-color="#1A1F1E"/>
<stop offset="0.55" stop-color="#111514"/>
<stop offset="1" stop-color="#0B0E0D"/>
<stop offset="0" stop-color="#202021"/>
<stop offset="0.55" stop-color="#131314"/>
<stop offset="1" stop-color="#0A0A0B"/>
</linearGradient>
<linearGradient id="brass" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="#F8D57C"/>
<stop offset="0.50" stop-color="#ECBD56"/>
<stop offset="1" stop-color="#D49E36"/>
<stop offset="0" stop-color="#FADD98"/>
<stop offset="0.46" stop-color="#EEBF64"/>
<stop offset="1" stop-color="#C78D2F"/>
</linearGradient>
<linearGradient id="slot" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#0F1312"/>
<stop offset="1" stop-color="#1D2321"/>
<stop offset="0" stop-color="#101011"/>
<stop offset="1" stop-color="#202021"/>
</linearGradient>
</defs>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

+23 -19
View File
@@ -1,10 +1,14 @@
// Renders the 1024x1024 MultiSimAdmin app icon with CoreGraphics only.
// Usage: swift Design/generate_app_icon.swift <output.png>
//
// The mark is the web favicon grown up: a flat brass SIM plate with three
// ascending slots cut out of it, on a graphite field. Two colours, one light
// source from the upper left, no metal texture, so it still reads at the 29pt
// settings size. The plate keeps the classic single SIM cut, top-left.
// The mark is the web favicon grown up: a flat gold SIM plate with three
// ascending slots cut out of it, on a neutral graphite field. Two colours, one
// light source from the upper left, no metal texture, so it still reads at the
// 29pt settings size. The plate keeps the classic single SIM cut, top-left.
//
// The field is deliberately neutral (R = G = B): the moment it drifts cool the
// whole icon reads as "blue icon" on a lit home screen. The gold is pushed warm
// and deep enough that it stays metal rather than mustard.
//
// Every coordinate is in visual space (origin top-left, y grows down).
import AppKit
@@ -129,16 +133,16 @@ func directionalRim(_ path: CGPath, _ rect: CGRect, _ width: CGFloat, _ stops: [
fillLinear(
rectPath(CGRect(x: 0, y: 0, width: side, height: side)),
[
(0.00, (26, 31, 30, 255)),
(0.55, (17, 21, 20, 255)),
(1.00, (11, 14, 13, 255)),
(0.00, (32, 32, 33, 255)),
(0.55, (19, 19, 20, 255)),
(1.00, (10, 10, 11, 255)),
] as [Stop],
CGPoint(x: side * 0.06, y: 0),
CGPoint(x: side * 0.94, y: side)
)
fillRadial(
CGPoint(x: side * 0.22, y: -side * 0.10), CGFloat(side * 0.86),
[(0.0, (214, 226, 218, 26)), (0.60, (214, 226, 218, 7)), (1.0, (214, 226, 218, 0))] as [Stop]
[(0.0, (232, 226, 214, 24)), (0.60, (232, 226, 214, 6)), (1.0, (232, 226, 214, 0))] as [Stop]
)
// MARK: - Plate
@@ -165,9 +169,9 @@ ctx.restoreGState()
fillLinear(
plate,
[
(0.00, (248, 213, 124, 255)),
(0.50, (236, 189, 86, 255)),
(1.00, (212, 158, 54, 255)),
(0.00, (250, 221, 152, 255)),
(0.46, (238, 191, 100, 255)),
(1.00, (199, 141, 47, 255)),
] as [Stop],
CGPoint(x: plateRect.minX, y: plateRect.minY),
CGPoint(x: plateRect.maxX, y: plateRect.maxY)
@@ -175,9 +179,9 @@ fillLinear(
directionalRim(
plate, plateRect, plateSide * 0.007,
[
(0.00, (255, 244, 214, 132)),
(0.36, (255, 238, 198, 30)),
(0.68, (255, 234, 186, 0)),
(0.00, (255, 246, 224, 140)),
(0.36, (255, 240, 206, 32)),
(0.68, (255, 236, 196, 0)),
] as [Stop]
)
@@ -203,8 +207,8 @@ for (index, height) in slotHeights.enumerated() {
fillLinear(
path,
[
(0.00, (15, 19, 18, 255)),
(1.00, (29, 35, 33, 255)),
(0.00, (16, 16, 17, 255)),
(1.00, (32, 32, 33, 255)),
] as [Stop],
CGPoint(x: rect.minX, y: rect.minY),
CGPoint(x: rect.minX, y: rect.maxY)
@@ -213,9 +217,9 @@ for (index, height) in slotHeights.enumerated() {
directionalRim(
path, rect, radius * 0.30,
[
(0.00, (120, 84, 18, 96)),
(0.52, (120, 84, 18, 18)),
(1.00, (255, 232, 176, 58)),
(0.00, (112, 74, 16, 100)),
(0.52, (112, 74, 16, 18)),
(1.00, (255, 236, 190, 60)),
] as [Stop]
)
}
@@ -2,5 +2,5 @@ public enum AppMetadata {
public static let name = "MultiSimAdmin"
public static let bundleIdentifier = "fun.chick.multi-simadmin"
public static let marketingVersion = "0.2.2"
public static let buildVersion = "7"
public static let buildVersion = "8"
}
@@ -102,10 +102,16 @@ struct FleetTagBatchSheet: View {
.accessibilityLabel("新标签名称")
.submitLabel(.done)
.onSubmit { submitDraft() }
.disabled(isBusy)
.disabled(isBusy || targets.isEmpty)
Button(isBusy ? "处理中" : "添加") { submitDraft() }
.buttonStyle(AIButtonStyle(kind: .primary, size: .regular, fixedWidth: 84))
.disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isBusy)
.disabled(draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isBusy || targets.isEmpty)
}
// 没有勾选节点时这个表单按下去只会清空输入,先说清楚为什么不能动。
if targets.isEmpty {
Text("勾选节点后即可新建标签并批量贴上。")
.font(.caption2)
.foregroundStyle(AITheme.faint)
}
}
}
@@ -135,44 +141,87 @@ struct FleetTagBatchSheet: View {
private var isComplete: Bool { total > 0 && covered == total }
private var isPartial: Bool { covered > 0 && covered < total }
// 没有勾选任何节点时这一整片都是不可操作状态,此时不该用「未贴」这种带暗示的
// 灰字,直接按中性标签渲染,避免看起来像加载失败的禁用控件。
private var isInert: Bool { total == 0 }
private var stateText: String {
if isInert { return "" }
if isComplete { return "全部" }
if isPartial { return "\(covered)/\(total)" }
return "未贴"
}
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
Image(systemName: isComplete ? "checkmark.circle.fill" : (isPartial ? "circle.lefthalf.filled" : "circle"))
.font(.system(size: 12, weight: .semibold))
.accessibilityHidden(true)
Text(title)
.font(.footnote.weight(isComplete || isPartial ? .semibold : .medium))
.lineLimit(1)
.truncationMode(.middle)
if isInert {
// 不可操作时不渲染 Button:系统禁用态会把整枚胶囊压成灰色,
// 看起来像加载失败,而标签本身是需要读清楚的内容。
label
.accessibilityLabel("标签 \(title),勾选节点后可贴上")
} else {
Button(action: action) { label }
.buttonStyle(.plain)
.disabled(isBusy)
.accessibilityLabel(isComplete ? "从已选节点移除标签 \(title)" : "给已选节点贴上标签 \(title)")
.accessibilityAddTraits(isComplete ? [.isButton, .isSelected] : .isButton)
}
}
private var label: some View {
HStack(spacing: 6) {
Image(systemName: iconName)
.font(.system(size: 12, weight: .semibold))
.accessibilityHidden(true)
Text(title)
.font(.footnote.weight(isComplete || isPartial ? .semibold : .medium))
.lineLimit(1)
.truncationMode(.middle)
if stateText.isEmpty == false {
Text(stateText)
.font(.caption2.weight(.semibold))
.monospacedDigit()
.opacity(0.72)
Spacer(minLength: 0)
}
.padding(.horizontal, 10)
.frame(height: 38)
.foregroundStyle(isComplete ? AITheme.onPrimary : (isPartial ? AITheme.ink : AITheme.muted))
.background(isComplete ? AITheme.primary : (isPartial ? AITheme.tile : AITheme.surface))
.clipShape(Capsule(style: .continuous))
.overlay {
Capsule(style: .continuous)
.strokeBorder(isComplete ? AITheme.primary : (isPartial ? AITheme.primary.opacity(0.42) : AITheme.line))
}
.contentShape(Capsule(style: .continuous))
Spacer(minLength: 0)
}
.buttonStyle(.plain)
.disabled(isBusy || total == 0)
.accessibilityLabel(isComplete ? "从已选节点移除标签 \(title)" : "给已选节点贴上标签 \(title)")
.accessibilityAddTraits(isComplete ? [.isButton, .isSelected] : .isButton)
.padding(.horizontal, 10)
.frame(height: 38)
.foregroundStyle(foreground)
.background(background)
.clipShape(Capsule(style: .continuous))
.overlay {
Capsule(style: .continuous)
.strokeBorder(border)
}
.contentShape(Capsule(style: .continuous))
}
private var iconName: String {
if isInert { return "tag" }
if isComplete { return "checkmark.circle.fill" }
if isPartial { return "circle.lefthalf.filled" }
return "circle"
}
private var foreground: Color {
if isInert { return AITheme.ink }
if isComplete { return AITheme.onPrimary }
if isPartial { return AITheme.ink }
return AITheme.muted
}
private var background: Color {
if isInert { return AITheme.tile }
if isComplete { return AITheme.primary }
if isPartial { return AITheme.tile }
return AITheme.surface
}
private var border: Color {
if isInert { return AITheme.tileLine }
if isComplete { return AITheme.primary }
if isPartial { return AITheme.primary.opacity(0.42) }
return AITheme.line
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

After

Width:  |  Height:  |  Size: 812 KiB

@@ -10,7 +10,7 @@
<key>CFBundleName</key><string>MultiSimAdmin</string>
<key>CFBundlePackageType</key><string>APPL</string>
<key>CFBundleShortVersionString</key><string>0.2.2</string>
<key>CFBundleVersion</key><string>7</string>
<key>CFBundleVersion</key><string>8</string>
<key>LSRequiresIPhoneOS</key><true/>
<key>MinimumOSVersion</key><string>16.0</string>
<key>NSAppTransportSecurity</key>
@@ -7,6 +7,6 @@ final class AppMetadataTests: XCTestCase {
XCTAssertEqual(AppMetadata.name, "MultiSimAdmin")
XCTAssertEqual(AppMetadata.bundleIdentifier, "fun.chick.multi-simadmin")
XCTAssertEqual(AppMetadata.marketingVersion, "0.2.2")
XCTAssertEqual(AppMetadata.buildVersion, "7")
XCTAssertEqual(AppMetadata.buildVersion, "8")
}
}
@@ -12,7 +12,7 @@ final class SettingsViewModelTests: XCTestCase {
XCTAssertEqual(model.runtime?.appName, "MultiSimAdmin")
XCTAssertEqual(model.runtime?.marketingVersion, "0.2.2")
XCTAssertEqual(model.runtime?.buildVersion, "7")
XCTAssertEqual(model.runtime?.buildVersion, "8")
XCTAssertEqual(model.runtime?.platform, "iOS")
XCTAssertTrue(model.runtime?.systemVersion.hasPrefix("iOS") ?? false)
+7 -3
View File
@@ -1,6 +1,6 @@
# iOS Native 0.2.2
0.2.2 构建 7 把「分组」这个实体从应用里彻底删掉,归类只靠标签;同时完成第二轮配色与图标重构,
0.2.2 构建 8 把「分组」这个实体从应用里彻底删掉,归类只靠标签;同时完成第二轮配色与图标重构,
界面上不再出现蓝色。页面能力、上游白名单与本地数据布局见现行说明 [docs/ios-native.md](ios-native.md),
本文只记录本次发布的变化。
@@ -18,6 +18,8 @@
改为实心主色加 `onPrimary` 文字,两套主题都一眼可读,且选中不改变控件尺寸。
- 应用图标重做:放弃立体卡与投影,改扁平石墨底加黄铜 SIM 面板,三根底部对齐的递增信号条,
29pt 下轮廓仍可读;面板边长、圆角、切角与信号条间距可用环境变量调参后重新光栅化。
底色与插槽改为严格等值的 R=G=B 中性石墨(原先偏青绿,在亮色壁纸上会被读成蓝),金色
下压饱和度、提高明度跨度,从浅金到深古铜,去掉「芥末黄」的塑料感。
- 运行概览收紧:检测进度从读数格移进卡片副标题(`刚刚更新 · 已检测 6/6 台`);读数栅格固定
八格两行,温度与电量即使整群节点都没上报也保留「不可用」占位,栅格不再在七格与九格之间跳;
概览量表健康时用墨色,只在告警与危险时上色。
@@ -32,6 +34,8 @@
卡片同一形状同一图标;消息中心的「收件 / 发件」由绿色改回中性胶囊配上下箭头,绿色重新只
表示健康状态。
- 批量标签表在没有任何勾选时给出「先在节点列表勾选要归类的节点」提示,不再留一行空白。
同时这段状态下标签胶囊不再渲染成 `Button`:系统禁用态会把整枚胶囊压成灰块,看起来像加载
失败,现按中性标签原样显示,只把「新建标签并贴上」的输入与按钮置灰并说明原因。
- 版本号显示去掉括号:设置页「应用信息」只显示 `0.2.2`。
## 文档同步
@@ -61,10 +65,10 @@
- `build/ipa/MultiSimAdmin.ipa`
- `build/ipa/MultiSimAdmin.ipa.sha256`
0.2.2 构建 7 校验值:
0.2.2 构建 8 校验值:
```text
0e4ffb74d03c2da5648e7bc039f67b14798e6d3aec723c206bce17da5b8f06fb MultiSimAdmin.ipa
a40e8e308f667edfe69902d5665a9ad85bdb52f72b5a248a671b6eef3591d847 MultiSimAdmin.ipa
```
IPA 目标为 iOS 16.0 及以上,`arm64`,Bundle ID 为 `fun.chick.multi-simadmin`,未签名,
+2 -2
View File
@@ -3,7 +3,7 @@
本文是 iPhone 应用的唯一现行说明,内容与仓库代码一致。历史设计稿和执行计划保留在
`docs/superpowers/` 下,其中已被实现推翻的决策在本文第 9 节逐条列出。
- 当前版本:`0.2.2`,构建号 7
- 当前版本:`0.2.2`,构建号 8
- 承载分支:`ios-native-ipa`
- 运行要求:iOS 16.0 及以上,`arm64`,Bundle ID `fun.chick.multi-simadmin`
- 产物:未签名 `MultiSimAdmin.ipa`,由 TrollStore 或用户自有签名流程安装
@@ -197,7 +197,7 @@ xcodebuild -scheme MultiSimAdmin \
## 11. 文档索引
- [docs/ios-native.md](ios-native.md):本文,现行说明
- [docs/ios-native-0.2.2.md](ios-native-0.2.2.md):0.2.2 构建 7 发布说明与校验值
- [docs/ios-native-0.2.2.md](ios-native-0.2.2.md):0.2.2 构建 8 发布说明与校验值
- [docs/ios-native-0.2.1.md](ios-native-0.2.1.md):0.2.1 构建 6 发布说明与校验值
- [docs/ios-native-0.2.0.md](ios-native-0.2.0.md):0.2.0 发布说明
- [docs/superpowers/specs/2026-09-08-ios-native-ipa-design.md](superpowers/specs/2026-09-08-ios-native-ipa-design.md):历史设计稿