import SwiftUI import XuqmSDK struct ChatView: View { let targetId: String let targetName: String let currentUserId: String @StateObject private var viewModel = ChatViewModel() @State private var scrollToBottom = false var body: some View { VStack(spacing: 0) { if viewModel.connectionStatus != "已连接" { Text(viewModel.connectionStatus) .font(.caption) .foregroundStyle(.white) .padding(.vertical, 4) .frame(maxWidth: .infinity) .background(viewModel.connectionStatus.contains("错误") ? Color.red : Color.orange) } ScrollViewReader { proxy in ScrollView { LazyVStack(spacing: 8) { ForEach(viewModel.messages.reversed(), id: \.id) { message in MessageBubble(message: message, currentUserId: currentUserId) .id(message.id) .rotationEffect(.degrees(180)) } if viewModel.isLoading { ProgressView() .padding() .rotationEffect(.degrees(180)) } } .padding() .rotationEffect(.degrees(180)) } .onChange(of: viewModel.messages.count) { _ in if let first = viewModel.messages.first { withAnimation { proxy.scrollTo(first.id, anchor: .bottom) } } } } Divider() HStack(spacing: 12) { TextField("输入消息…", text: $viewModel.inputText, axis: .vertical) .textFieldStyle(.roundedBorder) .lineLimit(1...4) Button { viewModel.sendText() } label: { Image(systemName: "paperplane.fill") .foregroundStyle(viewModel.inputText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? Color.gray : Color.accentColor) } .disabled(viewModel.inputText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } .padding() } .navigationTitle(targetName) #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif .onAppear { viewModel.setup(targetId: targetId, chatType: .single) viewModel.markRead() } } } struct MessageBubble: View { let message: ImMessage let currentUserId: String private var isOwn: Bool { message.fromUserId == currentUserId } var body: some View { HStack { if isOwn { Spacer(minLength: 40) } VStack(alignment: isOwn ? .trailing : .leading, spacing: 2) { Text(parseMessageText(message)) .padding(.horizontal, 12) .padding(.vertical, 8) .background(isOwn ? Color.accentColor.opacity(0.15) : Color.gray.opacity(0.2)) .clipShape(RoundedRectangle(cornerRadius: 16)) .foregroundStyle(.primary) HStack(spacing: 4) { if isOwn { Text(statusLabel(message.status)) .font(.caption2) .foregroundStyle(.secondary) } Text(formatConversationTime(message.createdAt)) .font(.caption2) .foregroundStyle(.secondary) } } if !isOwn { Spacer(minLength: 40) } } } }