release(ios): 发布 0.2.1 并重构图标与界面细节
- 应用图标改用与 Web favicon 同源的扁平标记,金色 SIM 卡体加三格递增信号,29pt 下仍可读 - 标签统一为胶囊形状,提高深色主题下的描边权重,避免标签在暗色下消失 - 消息卡片与详情分区卡片收紧行距,单屏信息密度明显提升 - 详情头部指标精简为 4 行,数据模块改为单条读数 - 批量选择勾选框移出名称区域,工具栏控件统一为等高 34pt - 分组编辑器改为点选即生效,移除说明文字与常驻禁用按钮
This commit is contained in:
@@ -12,24 +12,35 @@ struct MessagesView: View {
|
||||
ScrollView {
|
||||
LazyVStack(spacing: 12) {
|
||||
filterBar
|
||||
if let errorMessage = model.errorMessage {
|
||||
AIStatusBanner(
|
||||
errorMessage, tint: AITheme.warning, systemImage: "exclamationmark.triangle")
|
||||
}
|
||||
if model.isLoading && model.filteredRows.isEmpty {
|
||||
AILoadingCard("正在同步消息数据")
|
||||
}
|
||||
if model.filteredRows.isEmpty && !model.isLoading {
|
||||
} else if model.filteredRows.isEmpty {
|
||||
AICard {
|
||||
AIEmptyState(
|
||||
systemImage: "message",
|
||||
title: "暂无消息",
|
||||
message: "刷新后从实例同步短信。"
|
||||
)
|
||||
if let errorMessage = model.errorMessage {
|
||||
AIEmptyState(
|
||||
systemImage: "arrow.triangle.2.circlepath",
|
||||
title: "未能读取消息",
|
||||
message: errorMessage,
|
||||
actionTitle: "重试"
|
||||
) {
|
||||
Task { await model.refresh() }
|
||||
}
|
||||
} else {
|
||||
AIEmptyState(
|
||||
systemImage: "message",
|
||||
title: "暂无消息",
|
||||
message: "所有节点都没有返回短信。"
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let errorMessage = model.errorMessage {
|
||||
AIStatusBanner(
|
||||
errorMessage, tint: AITheme.warning, systemImage: "exclamationmark.triangle")
|
||||
}
|
||||
ForEach(model.filteredRows) { message in
|
||||
messageCard(message)
|
||||
}
|
||||
}
|
||||
ForEach(model.filteredRows) { message in
|
||||
messageCard(message)
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
@@ -45,6 +56,14 @@ struct MessagesView: View {
|
||||
}
|
||||
.background(AITheme.canvas.ignoresSafeArea())
|
||||
.navigationTitle("消息中心")
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
AIIconButton("刷新", systemImage: "arrow.clockwise") {
|
||||
Task { await model.refresh() }
|
||||
}
|
||||
.disabled(model.isLoading)
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"删除这条消息?操作不可恢复。",
|
||||
isPresented: Binding(
|
||||
@@ -71,68 +90,83 @@ struct MessagesView: View {
|
||||
}
|
||||
|
||||
private var filterBar: some View {
|
||||
AICard {
|
||||
VStack(spacing: 10) {
|
||||
AISegmentedControl(
|
||||
"消息方向",
|
||||
selection: $model.direction,
|
||||
options: MessagesViewModel.DirectionFilter.allCases.map {
|
||||
(value: $0, title: $0.rawValue)
|
||||
}
|
||||
)
|
||||
|
||||
Picker("实例", selection: $model.selectedInstanceID) {
|
||||
Text("全部实例").tag(Optional<UUID>.none)
|
||||
ForEach(model.instances) { instance in
|
||||
Text(instance.name).tag(Optional(instance.id))
|
||||
}
|
||||
VStack(spacing: 8) {
|
||||
AISegmentedControl(
|
||||
"消息方向",
|
||||
selection: $model.direction,
|
||||
options: MessagesViewModel.DirectionFilter.allCases.map {
|
||||
(value: $0, title: $0.rawValue)
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.aiSelectSurface()
|
||||
)
|
||||
|
||||
AISearchField("搜索号码或内容", text: $model.searchText)
|
||||
Picker("实例", selection: $model.selectedInstanceID) {
|
||||
Text("全部实例").tag(Optional<UUID>.none)
|
||||
ForEach(model.instances) { instance in
|
||||
Text(instance.name).tag(Optional(instance.id))
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.aiSelectSurface(compact: true)
|
||||
// 选择器右侧原本是一整片空白,这里补上当前条数,让这一行读完就知道结果规模。
|
||||
.overlay(alignment: .trailing) {
|
||||
Text("\(model.filteredRows.count) 条")
|
||||
.font(.caption.weight(.medium))
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.padding(.trailing, 12)
|
||||
.accessibilityHidden(true)
|
||||
}
|
||||
|
||||
AISearchField("搜索号码或内容", text: $model.searchText, minHeight: AITheme.compactControlHeight)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func messageCard(_ message: SMSMessage) -> some View {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack(spacing: 8) {
|
||||
AICard(padding: 14) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 8) {
|
||||
Text(message.phoneNumber.isEmpty ? "未知号码" : message.phoneNumber)
|
||||
.font(.headline)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundStyle(AITheme.ink)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.layoutPriority(1)
|
||||
Spacer()
|
||||
AITag(
|
||||
message.direction.label,
|
||||
tint: message.direction == .outgoing ? AITheme.primary : AITheme.success)
|
||||
Spacer(minLength: 8)
|
||||
Text(TimestampText.short(message.timestamp))
|
||||
.font(.caption2.weight(.medium))
|
||||
.monospacedDigit()
|
||||
.foregroundStyle(AITheme.faint)
|
||||
.lineLimit(1)
|
||||
.accessibilityLabel("时间 \(message.timestamp)")
|
||||
}
|
||||
Text(message.content)
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(AITheme.ink)
|
||||
.lineSpacing(2)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
HStack(alignment: .center, spacing: 10) {
|
||||
Text(message.instanceName)
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
Label(message.instanceName, systemImage: "antenna.radiowaves.left.and.right")
|
||||
.font(.caption)
|
||||
.labelStyle(.titleAndIcon)
|
||||
.imageScale(.small)
|
||||
.foregroundStyle(AITheme.muted)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.middle)
|
||||
.layoutPriority(1)
|
||||
if let delivery = message.deliveryLabel {
|
||||
Text(delivery)
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundStyle(delivery == "发送失败" ? AITheme.danger : AITheme.faint)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer(minLength: 8)
|
||||
Button {
|
||||
AISymbolAction("删除这条消息", systemImage: "trash", tint: AITheme.danger) {
|
||||
pendingDeleteMessage = message
|
||||
} label: {
|
||||
Label("删除", systemImage: "trash")
|
||||
}
|
||||
.buttonStyle(
|
||||
AIButtonStyle(
|
||||
kind: .danger,
|
||||
size: .compact,
|
||||
fixedWidth: AITheme.listActionButtonWidth
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user