- 添加 expiresAt 和 refreshUserSig 参数支持自动续签 - 修改 PushSDK 初始化方式,自动完成设备注册和厂商初始化 - 调整过期续签策略,从提前 15 分钟改为提前 5 分钟触发 - 重构 RN SDK 文档结构,简化安装和使用方式 - 更新统一登录流程,支持 profile 信息传递 - 添加 IM 数据库自动隔离功能 - 修复 Android 群消息聚合问题 - 补充自动化测试验证和错误处理机制
114 行
3.9 KiB
Swift
114 行
3.9 KiB
Swift
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) }
|
|
}
|
|
}
|
|
}
|