release(ios): 0.2.2 构建 10 修消息卡空洞、标签胶囊假宽与表单标题层级
- 消息卡片删除按钮移出正文行,改用 overlay 悬在右侧,44pt 点击区不再把行撑高 - 批量标签胶囊由等宽栅格改为流式布局,新增 AIFlowLayout 并让节点页复用同一实现 - 设置页应用信息补「构建」一行,版本号全程不带括号 - 添加实例与发送消息两张表单表统一行内标题,去掉大标题留下的空白带 - 节点空态文案改为「节点未返回设备信息」,与量表条实际返回的数据不矛盾 - 图标石墨底、面板阴影与插槽统一压暖 3~4 级,全图 0 蓝色像素
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## iOS Native 应用
|
## iOS Native 应用
|
||||||
|
|
||||||
`apps/ios` 提供不依赖 8788 控制台的原生 iPhone 应用,当前版本 **0.2.2**,构建号 9,运行在 iOS 16 及以上,产物为未签名 IPA,供 TrollStore 或自有签名流程安装。iOS 开发在 `ios-native-ipa` 分支进行。
|
`apps/ios` 提供不依赖 8788 控制台的原生 iPhone 应用,当前版本 **0.2.2**,构建号 10,运行在 iOS 16 及以上,产物为未签名 IPA,供 TrollStore 或自有签名流程安装。iOS 开发在 `ios-native-ipa` 分支进行。
|
||||||
|
|
||||||
底部为节点、消息、设置三个入口:
|
底部为节点、消息、设置三个入口:
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,12 @@
|
|||||||
// light source from the upper left, no metal texture, so it still reads at the
|
// 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.
|
// 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
|
// The field is warm-neutral, not mathematically neutral. A perfectly grey R=G=B
|
||||||
// whole icon reads as "blue icon" on a lit home screen. The gold is pushed warm
|
// plate sitting next to saturated gold loses the comparison and reads cool on a
|
||||||
// and deep enough that it stays metal rather than mustard.
|
// lit home screen — that is how "there is no blue in it" still looked "blue" to
|
||||||
|
// the eye. Red runs 2..4 levels over blue instead, which is invisible as a cast
|
||||||
|
// but impossible to misread. The gold stays warm and deep so it reads metal
|
||||||
|
// rather than mustard.
|
||||||
//
|
//
|
||||||
// Every coordinate is in visual space (origin top-left, y grows down).
|
// Every coordinate is in visual space (origin top-left, y grows down).
|
||||||
import AppKit
|
import AppKit
|
||||||
@@ -133,10 +136,10 @@ func directionalRim(_ path: CGPath, _ rect: CGRect, _ width: CGFloat, _ stops: [
|
|||||||
fillLinear(
|
fillLinear(
|
||||||
rectPath(CGRect(x: 0, y: 0, width: side, height: side)),
|
rectPath(CGRect(x: 0, y: 0, width: side, height: side)),
|
||||||
[
|
[
|
||||||
// 石墨底严格等色。原先 B 比 R 高 1 级,缩到 60pt 后边缘像素仍会落在蓝色波段。
|
// 石墨底走暖一档:R 比 B 高 4 级,肉眼看不出偏色,但再也不可能被读成冷调。
|
||||||
(0.00, (32, 32, 32, 255)),
|
(0.00, (34, 32, 30, 255)),
|
||||||
(0.55, (19, 19, 19, 255)),
|
(0.55, (20, 19, 18, 255)),
|
||||||
(1.00, (10, 10, 10, 255)),
|
(1.00, (11, 10, 10, 255)),
|
||||||
] as [Stop],
|
] as [Stop],
|
||||||
CGPoint(x: side * 0.06, y: 0),
|
CGPoint(x: side * 0.06, y: 0),
|
||||||
CGPoint(x: side * 0.94, y: side)
|
CGPoint(x: side * 0.94, y: side)
|
||||||
@@ -161,7 +164,7 @@ let plateRect = CGRect(
|
|||||||
let plate = simPath(plateRect, corner: plateCorner, chamfer: plateChamfer)
|
let plate = simPath(plateRect, corner: plateCorner, chamfer: plateChamfer)
|
||||||
|
|
||||||
ctx.saveGState()
|
ctx.saveGState()
|
||||||
ctx.setShadow(offset: CGSize(width: 0, height: -22), blur: 46, color: rgb(0, 3, 2, 150))
|
ctx.setShadow(offset: CGSize(width: 0, height: -22), blur: 46, color: rgb(2, 1, 0, 150))
|
||||||
ctx.addPath(plate)
|
ctx.addPath(plate)
|
||||||
ctx.setFillColor(rgb(0, 0, 0))
|
ctx.setFillColor(rgb(0, 0, 0))
|
||||||
ctx.fillPath()
|
ctx.fillPath()
|
||||||
@@ -211,8 +214,9 @@ for (index, height) in slotHeights.enumerated() {
|
|||||||
fillLinear(
|
fillLinear(
|
||||||
path,
|
path,
|
||||||
[
|
[
|
||||||
(0.00, (16, 16, 16, 255)),
|
// 插槽与底同色温,避免金面板上的洞看起来发青。
|
||||||
(1.00, (32, 32, 32, 255)),
|
(0.00, (17, 16, 14, 255)),
|
||||||
|
(1.00, (34, 32, 30, 255)),
|
||||||
] as [Stop],
|
] as [Stop],
|
||||||
CGPoint(x: rect.minX, y: rect.minY),
|
CGPoint(x: rect.minX, y: rect.minY),
|
||||||
CGPoint(x: rect.minX, y: rect.maxY)
|
CGPoint(x: rect.minX, y: rect.maxY)
|
||||||
|
|||||||
@@ -2,5 +2,5 @@ public enum AppMetadata {
|
|||||||
public static let name = "MultiSimAdmin"
|
public static let name = "MultiSimAdmin"
|
||||||
public static let bundleIdentifier = "fun.chick.multi-simadmin"
|
public static let bundleIdentifier = "fun.chick.multi-simadmin"
|
||||||
public static let marketingVersion = "0.2.2"
|
public static let marketingVersion = "0.2.2"
|
||||||
public static let buildVersion = "9"
|
public static let buildVersion = "10"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,11 +71,9 @@ struct FleetTagBatchSheet: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(AITheme.faint)
|
.foregroundStyle(AITheme.faint)
|
||||||
} else {
|
} else {
|
||||||
LazyVGrid(
|
// 与节点卡片的标签行同一排法:胶囊按标签长度取宽。栅格会把每枚拉到
|
||||||
columns: [GridItem(.adaptive(minimum: 112), alignment: .leading)],
|
// 同一列宽,短标签右侧空出一片,看起来像控件没加载完。
|
||||||
alignment: .leading,
|
AIFlowLayout(spacing: 8) {
|
||||||
spacing: 8
|
|
||||||
) {
|
|
||||||
ForEach(model.allTags, id: \.self) { tag in
|
ForEach(model.allTags, id: \.self) { tag in
|
||||||
CoverageChip(
|
CoverageChip(
|
||||||
title: tag,
|
title: tag,
|
||||||
@@ -87,6 +85,7 @@ struct FleetTagBatchSheet: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +181,6 @@ struct FleetTagBatchSheet: View {
|
|||||||
.monospacedDigit()
|
.monospacedDigit()
|
||||||
.opacity(0.72)
|
.opacity(0.72)
|
||||||
}
|
}
|
||||||
Spacer(minLength: 0)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 10)
|
||||||
.frame(height: 38)
|
.frame(height: 38)
|
||||||
|
|||||||
@@ -1170,7 +1170,9 @@ extension FleetView {
|
|||||||
.padding(.vertical, 9)
|
.padding(.vertical, 9)
|
||||||
.accessibilityLabel("正在读取运行信息")
|
.accessibilityLabel("正在读取运行信息")
|
||||||
} else {
|
} else {
|
||||||
Text("节点未返回运行信息")
|
// 这一区只放设备与容量读数;信号、延迟这些运行指标在上方的量表里,
|
||||||
|
// 说「未返回运行信息」会和同一张卡片上的延迟读数自相矛盾。
|
||||||
|
Text("节点未返回设备信息")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(AITheme.faint)
|
.foregroundStyle(AITheme.faint)
|
||||||
.padding(.vertical, 9)
|
.padding(.vertical, 9)
|
||||||
@@ -1479,59 +1481,8 @@ extension FleetView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FlowLayout: Layout {
|
/// 流式排布已提到主题层,这里保留原名,卡片标签行不用改调用点。
|
||||||
let spacing: CGFloat
|
typealias FlowLayout = AIFlowLayout
|
||||||
|
|
||||||
func sizeThatFits(
|
|
||||||
proposal: ProposedViewSize,
|
|
||||||
subviews: Subviews,
|
|
||||||
cache: inout ()
|
|
||||||
) -> CGSize {
|
|
||||||
let maxWidth = proposal.width ?? .infinity
|
|
||||||
var x: CGFloat = 0
|
|
||||||
var y: CGFloat = 0
|
|
||||||
var rowHeight: CGFloat = 0
|
|
||||||
var rowWidth: CGFloat = 0
|
|
||||||
|
|
||||||
for subview in subviews {
|
|
||||||
let size = subview.sizeThatFits(.unspecified)
|
|
||||||
if x > 0, x + size.width > maxWidth {
|
|
||||||
x = 0
|
|
||||||
y += rowHeight + spacing
|
|
||||||
rowHeight = 0
|
|
||||||
rowWidth = 0
|
|
||||||
}
|
|
||||||
x += size.width + spacing
|
|
||||||
rowHeight = max(rowHeight, size.height)
|
|
||||||
rowWidth = max(rowWidth, x - spacing)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CGSize(width: rowWidth, height: max(0, y + rowHeight - (rowHeight > 0 ? 0 : spacing)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func placeSubviews(
|
|
||||||
in bounds: CGRect,
|
|
||||||
proposal: ProposedViewSize,
|
|
||||||
subviews: Subviews,
|
|
||||||
cache: inout ()
|
|
||||||
) {
|
|
||||||
var x = bounds.minX
|
|
||||||
var y = bounds.minY
|
|
||||||
var rowHeight: CGFloat = 0
|
|
||||||
|
|
||||||
for subview in subviews {
|
|
||||||
let size = subview.sizeThatFits(.unspecified)
|
|
||||||
if x > bounds.minX, x + size.width > bounds.maxX {
|
|
||||||
x = bounds.minX
|
|
||||||
y += rowHeight + spacing
|
|
||||||
rowHeight = 0
|
|
||||||
}
|
|
||||||
subview.place(at: CGPoint(x: x, y: y), proposal: .unspecified)
|
|
||||||
x += size.width + spacing
|
|
||||||
rowHeight = max(rowHeight, size.height)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FleetMetric: View {
|
struct FleetMetric: View {
|
||||||
let label: String
|
let label: String
|
||||||
|
|||||||
@@ -73,6 +73,9 @@ struct InstanceFormView: View {
|
|||||||
.background(AITheme.canvas.ignoresSafeArea())
|
.background(AITheme.canvas.ignoresSafeArea())
|
||||||
.navigationTitle(instance == nil ? "添加实例" : "编辑实例")
|
.navigationTitle(instance == nil ? "添加实例" : "编辑实例")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
// 表单表统一用行内标题:大标题会在卡片上方留出一整条空白,
|
||||||
|
// 而「批量标签」等其它表已经是行内样式。
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.scrollDismissesKeyboard(.interactively)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,15 @@ struct MessagesView: View {
|
|||||||
@State private var pendingDeleteMessage: SMSMessage?
|
@State private var pendingDeleteMessage: SMSMessage?
|
||||||
|
|
||||||
init(model: MessagesViewModel? = nil) {
|
init(model: MessagesViewModel? = nil) {
|
||||||
_model = StateObject(wrappedValue: model ?? DefaultContainer.makeMessagesModel())
|
// StateObject 的 wrappedValue 是 autoclosure,在 init 里改它会写到另一个实例上,
|
||||||
|
// 所以调试开关必须先落到这个具体的 model 对象。
|
||||||
|
let target = model ?? DefaultContainer.makeMessagesModel()
|
||||||
|
#if DEBUG
|
||||||
|
if DebugLaunch.has("--sheet-send") {
|
||||||
|
target.showingSendForm = true
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_model = StateObject(wrappedValue: target)
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -147,6 +155,9 @@ struct MessagesView: View {
|
|||||||
.foregroundStyle(AITheme.ink)
|
.foregroundStyle(AITheme.ink)
|
||||||
.lineSpacing(2)
|
.lineSpacing(2)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
// 删除按钮的点击区是 44pt,留在行内会把这一行撑到 44 高,正文与落款之间
|
||||||
|
// 因此凭空多出一条约 22pt 的空带。这里让落款按自身高度排,点击区挂在
|
||||||
|
// 行尾并垂直居中,多出的 12pt 落进卡片内边距,正文右缘留出等宽避让。
|
||||||
HStack(alignment: .center, spacing: 8) {
|
HStack(alignment: .center, spacing: 8) {
|
||||||
Label(message.instanceName, systemImage: "antenna.radiowaves.left.and.right")
|
Label(message.instanceName, systemImage: "antenna.radiowaves.left.and.right")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -162,10 +173,15 @@ struct MessagesView: View {
|
|||||||
.foregroundStyle(delivery == "发送失败" ? AITheme.danger : AITheme.faint)
|
.foregroundStyle(delivery == "发送失败" ? AITheme.danger : AITheme.faint)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
}
|
}
|
||||||
Spacer(minLength: 8)
|
}
|
||||||
|
.padding(.trailing, 44)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.overlay(alignment: .trailing) {
|
||||||
AISymbolAction("删除这条消息", systemImage: "trash", tint: AITheme.danger, quiet: true) {
|
AISymbolAction("删除这条消息", systemImage: "trash", tint: AITheme.danger, quiet: true) {
|
||||||
pendingDeleteMessage = message
|
pendingDeleteMessage = message
|
||||||
}
|
}
|
||||||
|
// 图标在 44pt 点击区里居中,右移半个内边距让图标右缘与正文右缘对齐。
|
||||||
|
.offset(x: 12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,8 @@ struct SendMessageFormView: View {
|
|||||||
.background(AITheme.canvas.ignoresSafeArea())
|
.background(AITheme.canvas.ignoresSafeArea())
|
||||||
.navigationTitle("发送消息")
|
.navigationTitle("发送消息")
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
|
// 与「添加实例」一致:表行内标题,避免大标题在表单上方留出一整条空白。
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.scrollDismissesKeyboard(.interactively)
|
.scrollDismissesKeyboard(.interactively)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ struct SettingsView: View {
|
|||||||
AISectionTitle("应用信息", systemImage: "info.circle")
|
AISectionTitle("应用信息", systemImage: "info.circle")
|
||||||
if let runtime = model.runtime {
|
if let runtime = model.runtime {
|
||||||
AIKeyValueRow("版本", value: runtime.marketingVersion)
|
AIKeyValueRow("版本", value: runtime.marketingVersion)
|
||||||
|
// 构建号单独一行:同一营销版本会连续出多个包,只写版本号分不清装的是哪个。
|
||||||
|
AIKeyValueRow("构建", value: runtime.buildVersion)
|
||||||
AIKeyValueRow("系统", value: runtime.systemVersion)
|
AIKeyValueRow("系统", value: runtime.systemVersion)
|
||||||
} else {
|
} else {
|
||||||
Text("正在读取应用信息…")
|
Text("正在读取应用信息…")
|
||||||
|
|||||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 809 KiB After Width: | Height: | Size: 970 KiB |
@@ -10,7 +10,7 @@
|
|||||||
<key>CFBundleName</key><string>MultiSimAdmin</string>
|
<key>CFBundleName</key><string>MultiSimAdmin</string>
|
||||||
<key>CFBundlePackageType</key><string>APPL</string>
|
<key>CFBundlePackageType</key><string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key><string>0.2.2</string>
|
<key>CFBundleShortVersionString</key><string>0.2.2</string>
|
||||||
<key>CFBundleVersion</key><string>9</string>
|
<key>CFBundleVersion</key><string>10</string>
|
||||||
<key>LSRequiresIPhoneOS</key><true/>
|
<key>LSRequiresIPhoneOS</key><true/>
|
||||||
<key>MinimumOSVersion</key><string>16.0</string>
|
<key>MinimumOSVersion</key><string>16.0</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
|
|||||||
@@ -862,3 +862,63 @@ public struct AISymbolAction: View {
|
|||||||
.accessibilityLabel(title)
|
.accessibilityLabel(title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 按固有尺寸换行排布的流式布局:标签胶囊这类「宽度取决于文字」的控件用它。
|
||||||
|
/// 栅格会把每枚胶囊拉到同一列宽,短标签右侧就空出一片,读起来像没加载完。
|
||||||
|
public struct AIFlowLayout: Layout {
|
||||||
|
public let spacing: CGFloat
|
||||||
|
|
||||||
|
public init(spacing: CGFloat = 8) {
|
||||||
|
self.spacing = spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sizeThatFits(
|
||||||
|
proposal: ProposedViewSize,
|
||||||
|
subviews: Subviews,
|
||||||
|
cache: inout ()
|
||||||
|
) -> CGSize {
|
||||||
|
let maxWidth = proposal.width ?? .infinity
|
||||||
|
var x: CGFloat = 0
|
||||||
|
var y: CGFloat = 0
|
||||||
|
var rowHeight: CGFloat = 0
|
||||||
|
var widestRow: CGFloat = 0
|
||||||
|
|
||||||
|
for subview in subviews {
|
||||||
|
let size = subview.sizeThatFits(.unspecified)
|
||||||
|
if x > 0, x + size.width > maxWidth {
|
||||||
|
widestRow = max(widestRow, x - spacing)
|
||||||
|
y += rowHeight + spacing
|
||||||
|
x = 0
|
||||||
|
rowHeight = 0
|
||||||
|
}
|
||||||
|
x += size.width + spacing
|
||||||
|
rowHeight = max(rowHeight, size.height)
|
||||||
|
}
|
||||||
|
widestRow = max(widestRow, x - spacing)
|
||||||
|
|
||||||
|
return CGSize(width: max(0, widestRow), height: max(0, y + rowHeight))
|
||||||
|
}
|
||||||
|
|
||||||
|
public func placeSubviews(
|
||||||
|
in bounds: CGRect,
|
||||||
|
proposal: ProposedViewSize,
|
||||||
|
subviews: Subviews,
|
||||||
|
cache: inout ()
|
||||||
|
) {
|
||||||
|
var x = bounds.minX
|
||||||
|
var y = bounds.minY
|
||||||
|
var rowHeight: CGFloat = 0
|
||||||
|
|
||||||
|
for subview in subviews {
|
||||||
|
let size = subview.sizeThatFits(.unspecified)
|
||||||
|
if x > bounds.minX, x + size.width > bounds.maxX {
|
||||||
|
x = bounds.minX
|
||||||
|
y += rowHeight + spacing
|
||||||
|
rowHeight = 0
|
||||||
|
}
|
||||||
|
subview.place(at: CGPoint(x: x, y: y), proposal: .unspecified)
|
||||||
|
x += size.width + spacing
|
||||||
|
rowHeight = max(rowHeight, size.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,6 @@ final class AppMetadataTests: XCTestCase {
|
|||||||
XCTAssertEqual(AppMetadata.name, "MultiSimAdmin")
|
XCTAssertEqual(AppMetadata.name, "MultiSimAdmin")
|
||||||
XCTAssertEqual(AppMetadata.bundleIdentifier, "fun.chick.multi-simadmin")
|
XCTAssertEqual(AppMetadata.bundleIdentifier, "fun.chick.multi-simadmin")
|
||||||
XCTAssertEqual(AppMetadata.marketingVersion, "0.2.2")
|
XCTAssertEqual(AppMetadata.marketingVersion, "0.2.2")
|
||||||
XCTAssertEqual(AppMetadata.buildVersion, "9")
|
XCTAssertEqual(AppMetadata.buildVersion, "10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ final class SettingsViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
XCTAssertEqual(model.runtime?.appName, "MultiSimAdmin")
|
XCTAssertEqual(model.runtime?.appName, "MultiSimAdmin")
|
||||||
XCTAssertEqual(model.runtime?.marketingVersion, "0.2.2")
|
XCTAssertEqual(model.runtime?.marketingVersion, "0.2.2")
|
||||||
XCTAssertEqual(model.runtime?.buildVersion, "9")
|
XCTAssertEqual(model.runtime?.buildVersion, "10")
|
||||||
XCTAssertEqual(model.runtime?.platform, "iOS")
|
XCTAssertEqual(model.runtime?.platform, "iOS")
|
||||||
XCTAssertTrue(model.runtime?.systemVersion.hasPrefix("iOS") ?? false)
|
XCTAssertTrue(model.runtime?.systemVersion.hasPrefix("iOS") ?? false)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
0.2.2 把「分组」这个实体从应用里彻底删掉,归类只靠标签;同时完成第二轮配色与图标重构,界面上
|
0.2.2 把「分组」这个实体从应用里彻底删掉,归类只靠标签;同时完成第二轮配色与图标重构,界面上
|
||||||
不再出现蓝色。构建 9 是一轮收尾打磨:修掉消息中心发不出去信的按钮遮挡,补齐按钮层级,统一缺数据
|
不再出现蓝色。构建 9 是一轮收尾打磨:修掉消息中心发不出去信的按钮遮挡,补齐按钮层级,统一缺数据
|
||||||
读数的写法,并把中性灰阶压成严格等色。页面能力、上游白名单与本地数据布局见现行说明
|
读数的写法,并把中性灰阶压成严格等色。构建 10 继续收细节:消息卡片右侧的空洞、批量标签胶囊的
|
||||||
|
假宽度、设置页读不出构建号、两张表单表的标题层级,以及图标石墨底的冷调。页面能力、上游白名单与
|
||||||
|
本地数据布局见现行说明
|
||||||
[docs/ios-native.md](ios-native.md),
|
[docs/ios-native.md](ios-native.md),
|
||||||
本文只记录本次发布的变化。
|
本文只记录本次发布的变化。
|
||||||
|
|
||||||
@@ -63,6 +65,24 @@
|
|||||||
失败,现按中性标签原样显示,只把「新建标签并贴上」的输入与按钮置灰并说明原因。
|
失败,现按中性标签原样显示,只把「新建标签并贴上」的输入与按钮置灰并说明原因。
|
||||||
- 版本号显示去掉括号:设置页「应用信息」只显示 `0.2.2`。
|
- 版本号显示去掉括号:设置页「应用信息」只显示 `0.2.2`。
|
||||||
|
|
||||||
|
## 构建 10
|
||||||
|
|
||||||
|
- 消息卡片右侧的空洞修掉:删除按钮此前和摘要挤在同一行 `HStack` 里,它自带 44pt 点击区,等于把
|
||||||
|
这一行撑高约 22pt,卡片下半截全是空白,一屏只能放三张。现在摘要行按正文列宽排版,删除按钮用
|
||||||
|
`overlay` 悬在右侧并溢出到卡片留白里,点击区仍是 44pt,一屏回到四张。
|
||||||
|
- 批量标签的胶囊改成贴合文字宽度:原先用 `LazyVGrid` 等宽栅格,短标签被拉到 112pt 一格,看起来
|
||||||
|
像整片禁用的灰块。现在换成流式布局(`AIFlowLayout`),胶囊只占文字加内边距,节点页的标签条
|
||||||
|
复用同一份实现,删掉了原来那份重复的布局代码。
|
||||||
|
- 设置页显示构建号:同一营销版本会连续出多个包,只写 `0.2.2` 分不清设备上装的是哪个。应用信息
|
||||||
|
现在是「版本 0.2.2 / 构建 10 / 系统 iOS 26.3.1」三行,全程不带括号。
|
||||||
|
- 两张表单表统一行内标题:「添加实例」和「发送消息」用大标题,标题下方留出一整条空白,而同类的
|
||||||
|
「批量标签」已经是行内样式。现在三张表一致。
|
||||||
|
- 节点卡片的空态文案不再自相矛盾:卡片下方的量表条确实会返回运行时数据,空态却写「节点未返回
|
||||||
|
运行信息」。改为「节点未返回设备信息」,与实际缺少的字段一致。
|
||||||
|
- 图标底色压暖:石墨底原先是数学意义上的中性灰(R=G=B),在 iOS 浅色壁纸旁仍会被对比成冷调,
|
||||||
|
这是用户反馈「图标是蓝色」的直接来源(旧包 0.2.1 的图标确实有 72% 像素落在蓝色波段)。现在
|
||||||
|
底色、面板阴影与插槽统一带 3~4 级暖偏移,取色为 `rgb(47,45,42)`,全图 0 蓝色像素。
|
||||||
|
|
||||||
## 文档同步
|
## 文档同步
|
||||||
|
|
||||||
- 新增本文,[docs/ios-native.md](ios-native.md) 同步到 0.2.2:导航表去掉分组视图,节点总览与
|
- 新增本文,[docs/ios-native.md](ios-native.md) 同步到 0.2.2:导航表去掉分组视图,节点总览与
|
||||||
@@ -90,10 +110,10 @@
|
|||||||
- `build/ipa/MultiSimAdmin.ipa`
|
- `build/ipa/MultiSimAdmin.ipa`
|
||||||
- `build/ipa/MultiSimAdmin.ipa.sha256`
|
- `build/ipa/MultiSimAdmin.ipa.sha256`
|
||||||
|
|
||||||
0.2.2 构建 9 校验值:
|
0.2.2 构建 10 校验值:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
a0b90a3754bd5cb8d9b95906eb711b7d140d603b6f821ee838edb273e62e3d75 MultiSimAdmin.ipa
|
5018b9b4a1b236892b539746994176e1421a2cbe6ec24fdaac540d9bcbcf5fdd MultiSimAdmin.ipa
|
||||||
```
|
```
|
||||||
|
|
||||||
IPA 目标为 iOS 16.0 及以上,`arm64`,Bundle ID 为 `fun.chick.multi-simadmin`,未签名,
|
IPA 目标为 iOS 16.0 及以上,`arm64`,Bundle ID 为 `fun.chick.multi-simadmin`,未签名,
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
本文是 iPhone 应用的唯一现行说明,内容与仓库代码一致。历史设计稿和执行计划保留在
|
本文是 iPhone 应用的唯一现行说明,内容与仓库代码一致。历史设计稿和执行计划保留在
|
||||||
`docs/superpowers/` 下,其中已被实现推翻的决策在本文第 9 节逐条列出。
|
`docs/superpowers/` 下,其中已被实现推翻的决策在本文第 9 节逐条列出。
|
||||||
|
|
||||||
- 当前版本:`0.2.2`,构建号 9
|
- 当前版本:`0.2.2`,构建号 10
|
||||||
- 承载分支:`ios-native-ipa`
|
- 承载分支:`ios-native-ipa`
|
||||||
- 运行要求:iOS 16.0 及以上,`arm64`,Bundle ID `fun.chick.multi-simadmin`
|
- 运行要求:iOS 16.0 及以上,`arm64`,Bundle ID `fun.chick.multi-simadmin`
|
||||||
- 产物:未签名 `MultiSimAdmin.ipa`,由 TrollStore 或用户自有签名流程安装
|
- 产物:未签名 `MultiSimAdmin.ipa`,由 TrollStore 或用户自有签名流程安装
|
||||||
@@ -198,7 +198,7 @@ xcodebuild -scheme MultiSimAdmin \
|
|||||||
## 11. 文档索引
|
## 11. 文档索引
|
||||||
|
|
||||||
- [docs/ios-native.md](ios-native.md):本文,现行说明
|
- [docs/ios-native.md](ios-native.md):本文,现行说明
|
||||||
- [docs/ios-native-0.2.2.md](ios-native-0.2.2.md):0.2.2 构建 9 发布说明与校验值
|
- [docs/ios-native-0.2.2.md](ios-native-0.2.2.md):0.2.2 构建 10 发布说明与校验值
|
||||||
- [docs/ios-native-0.2.1.md](ios-native-0.2.1.md):0.2.1 构建 6 发布说明与校验值
|
- [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/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):历史设计稿
|
- [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