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:
@@ -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` 分支进行。
|
||||
|
||||
底部为节点、消息、设置三个入口:
|
||||
|
||||
|
||||
@@ -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 |
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
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)
|
||||
|
||||
|
||||
@@ -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
@@ -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):历史设计稿
|
||||
|
||||
Reference in New Issue
Block a user