feat(ios): refine native interface and app icon
Unify bottom actions, selection controls, navigation structure, spacing, and theme colors across iOS pages. Replace the app icon with a higher-contrast native-style asset and include its source SVG.
This commit is contained in:
@@ -1,97 +1,124 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SendMessageFormView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@ObservedObject var model: MessagesViewModel
|
||||
@State private var phoneNumber = ""
|
||||
@State private var content = ""
|
||||
@State private var validationMessage: String?
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@ObservedObject var model: MessagesViewModel
|
||||
@State private var phoneNumber = ""
|
||||
@State private var content = ""
|
||||
@State private var validationMessage: String?
|
||||
@State private var isSending = false
|
||||
|
||||
private var trimmedPhoneNumber: String {
|
||||
phoneNumber.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
private var trimmedPhoneNumber: String {
|
||||
phoneNumber.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
private var trimmedContent: String {
|
||||
content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
private var trimmedContent: String {
|
||||
content.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
}
|
||||
|
||||
private var canSend: Bool {
|
||||
!trimmedPhoneNumber.isEmpty && !trimmedContent.isEmpty
|
||||
}
|
||||
private var canSend: Bool {
|
||||
!model.instances.isEmpty && !trimmedPhoneNumber.isEmpty && !trimmedContent.isEmpty
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
AITheme.canvas.ignoresSafeArea()
|
||||
ScrollView {
|
||||
VStack(spacing: 12) {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("实例")
|
||||
Picker("实例", selection: Binding(
|
||||
get: { model.selectedInstanceID ?? model.instances.first?.id },
|
||||
set: { model.selectedInstanceID = $0 }
|
||||
)) {
|
||||
ForEach(model.instances) { instance in
|
||||
Text(instance.name).tag(Optional(instance.id))
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
.aiSelectSurface()
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("收件号码")
|
||||
TextField("收件号码", text: $phoneNumber)
|
||||
.textFieldStyle(AIFieldStyle())
|
||||
#if os(iOS)
|
||||
.keyboardType(.phonePad)
|
||||
#endif
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("消息内容")
|
||||
TextField("消息内容", text: $content, axis: .vertical)
|
||||
.textFieldStyle(AIFieldStyle())
|
||||
.lineLimit(4...8)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let validationMessage {
|
||||
AIStatusBanner(validationMessage, tint: AITheme.danger, systemImage: "exclamationmark.triangle")
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ScrollView {
|
||||
VStack(spacing: 12) {
|
||||
AICard {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("实例")
|
||||
Picker(
|
||||
"实例",
|
||||
selection: Binding(
|
||||
get: { model.selectedInstanceID ?? model.instances.first?.id },
|
||||
set: { model.selectedInstanceID = $0 }
|
||||
)
|
||||
) {
|
||||
if model.instances.isEmpty {
|
||||
Text("暂无实例").tag(Optional<UUID>.none)
|
||||
}
|
||||
ForEach(model.instances) { instance in
|
||||
Text(instance.name).tag(Optional(instance.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("发送消息")
|
||||
.aiBottomActionBar {
|
||||
HStack(spacing: 10) {
|
||||
Button("取消") { dismiss() }
|
||||
.buttonStyle(AIButtonStyle(kind: .secondary, size: .regular, fillsWidth: true))
|
||||
.frame(maxWidth: .infinity)
|
||||
.pickerStyle(.menu)
|
||||
.aiSelectSurface()
|
||||
}
|
||||
|
||||
Button("发送") { send() }
|
||||
.buttonStyle(AIButtonStyle(kind: .primary, size: .regular, fillsWidth: true))
|
||||
.disabled(!canSend)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("收件号码")
|
||||
TextField("收件号码", text: $phoneNumber)
|
||||
.textFieldStyle(AIFieldStyle())
|
||||
.accessibilityLabel("收件号码")
|
||||
.submitLabel(.next)
|
||||
#if os(iOS)
|
||||
.keyboardType(.phonePad)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func send() {
|
||||
Task {
|
||||
do {
|
||||
try await model.send(phoneNumber: trimmedPhoneNumber, content: trimmedContent)
|
||||
phoneNumber = ""
|
||||
content = ""
|
||||
validationMessage = nil
|
||||
dismiss()
|
||||
} catch {
|
||||
validationMessage = "发送失败,请检查号码、内容和实例登录状态。"
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
AIFieldLabel("消息内容")
|
||||
TextField("消息内容", text: $content, axis: .vertical)
|
||||
.textFieldStyle(AIFieldStyle())
|
||||
.accessibilityLabel("消息内容")
|
||||
.submitLabel(.send)
|
||||
.onSubmit {
|
||||
if canSend { send() }
|
||||
}
|
||||
.lineLimit(4...8)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let validationMessage {
|
||||
AIStatusBanner(
|
||||
validationMessage, tint: AITheme.danger, systemImage: "exclamationmark.triangle")
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
.aiBottomActionBar {
|
||||
AIActionRow {
|
||||
Button("取消") { dismiss() }
|
||||
.buttonStyle(AIButtonStyle(kind: .secondary, size: .regular, fillsWidth: true))
|
||||
|
||||
Button {
|
||||
send()
|
||||
} label: {
|
||||
HStack(spacing: 8) {
|
||||
if isSending {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
}
|
||||
Text(isSending ? "发送中" : "发送")
|
||||
}
|
||||
}
|
||||
.buttonStyle(AIButtonStyle(kind: .primary, size: .regular, fillsWidth: true))
|
||||
.disabled(!canSend || isSending)
|
||||
}
|
||||
}
|
||||
.background(AITheme.canvas.ignoresSafeArea())
|
||||
.navigationTitle("发送消息")
|
||||
#if os(iOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
private func send() {
|
||||
guard !isSending else { return }
|
||||
isSending = true
|
||||
Task {
|
||||
do {
|
||||
try await model.send(phoneNumber: trimmedPhoneNumber, content: trimmedContent)
|
||||
phoneNumber = ""
|
||||
content = ""
|
||||
validationMessage = nil
|
||||
dismiss()
|
||||
} catch {
|
||||
validationMessage = "发送失败,请检查号码、内容和实例登录状态。"
|
||||
}
|
||||
isSending = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user