feat(chat): 添加聊天界面视图模型和联系人管理功能
- 实现 ChatViewModel 处理消息收发、历史记录加载和状态管理 - 添加消息搜索、草稿保存、引用回复等功能 - 实现多媒体附件发送包括图片、视频、音频和文件 - 添加群组提及用户功能和消息撤回机制 - 实现联系人管理功能包括好友搜索、添加、删除和黑名单管理 - 添加好友请求处理和实时消息监听 - 实现会话列表管理包含未读消息统计和实时更新 - 集成 IM SDK 的连接状态管理和事件监听 - 添加消息状态跟踪和超时处理机制 - 实现数据缓存机制优化用户体验
这个提交包含在:
父节点
5eb46f97c6
当前提交
437e4b042a
@ -84,6 +84,7 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
status: .sending,
|
||||
mentionedUserIds: mentionedUserIds?.isEmpty == false ? mentionedUserIds : nil,
|
||||
groupReadCount: nil,
|
||||
revoked: false,
|
||||
createdAt: now
|
||||
)
|
||||
guard let activeAppId else {
|
||||
@ -199,6 +200,9 @@ public final class ImClient: NSObject, URLSessionWebSocketDelegate, @unchecked S
|
||||
let msg = try? JSONDecoder().decode(ImMessage.self, from: messageData) else {
|
||||
continue
|
||||
}
|
||||
if (msg.revoked ?? false) || msg.status == .revoked || msg.msgType == .revoked {
|
||||
delegate?.imClientDidReceiveRevokedMessage(msg)
|
||||
}
|
||||
if msg.chatType == .group {
|
||||
delegate?.imClientDidReceiveGroupMessage(msg)
|
||||
} else {
|
||||
@ -298,6 +302,7 @@ private extension ImMessage {
|
||||
status: .failed,
|
||||
mentionedUserIds: mentionedUserIds,
|
||||
groupReadCount: groupReadCount,
|
||||
revoked: false,
|
||||
createdAt: createdAt
|
||||
)
|
||||
}
|
||||
|
||||
@ -248,6 +248,16 @@ public final class ImSDK {
|
||||
client?.revoke(messageId: messageId)
|
||||
}
|
||||
|
||||
public func editMessage(messageId: String, content: String) async throws -> ImMessage {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
return try await ApiClient.shared.request(
|
||||
path: "/api/im/messages/\(messageId)",
|
||||
method: "PUT",
|
||||
queryItems: [URLQueryItem(name: "appId", value: config.appId)],
|
||||
body: EditMessageRequest(content: content)
|
||||
)
|
||||
}
|
||||
|
||||
public func subscribeGroup(_ groupId: String) {
|
||||
client?.subscribeGroup(groupId)
|
||||
}
|
||||
@ -274,6 +284,7 @@ public final class ImSDK {
|
||||
status: .failed,
|
||||
mentionedUserIds: mentionedUserIds,
|
||||
groupReadCount: nil,
|
||||
revoked: false,
|
||||
createdAt: Int64(Date().timeIntervalSince1970 * 1000)
|
||||
)
|
||||
}
|
||||
@ -319,6 +330,57 @@ public final class ImSDK {
|
||||
)
|
||||
}
|
||||
|
||||
public func locateHistoryPage(
|
||||
toId: String,
|
||||
messageId: String,
|
||||
pageSize: Int = 20,
|
||||
maxPages: Int = 20
|
||||
) async throws -> [ImMessage]? {
|
||||
try await locatePage(
|
||||
messageId: messageId,
|
||||
maxPages: maxPages,
|
||||
pageSize: pageSize,
|
||||
loadPage: { page in
|
||||
try await self.fetchHistory(toId: toId, page: page, size: pageSize)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
public func locateGroupHistoryPage(
|
||||
groupId: String,
|
||||
messageId: String,
|
||||
pageSize: Int = 20,
|
||||
maxPages: Int = 20
|
||||
) async throws -> [ImMessage]? {
|
||||
try await locatePage(
|
||||
messageId: messageId,
|
||||
maxPages: maxPages,
|
||||
pageSize: pageSize,
|
||||
loadPage: { page in
|
||||
try await self.fetchGroupHistory(groupId: groupId, page: page, size: pageSize)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func locatePage(
|
||||
messageId: String,
|
||||
maxPages: Int,
|
||||
pageSize: Int,
|
||||
loadPage: @escaping (Int) async throws -> [ImMessage]
|
||||
) async throws -> [ImMessage]? {
|
||||
let pageCount = max(maxPages, 1)
|
||||
for page in 0..<pageCount {
|
||||
let messages = try await loadPage(page)
|
||||
if messages.contains(where: { $0.id == messageId }) {
|
||||
return messages
|
||||
}
|
||||
if messages.count < pageSize {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func listGroups() async throws -> [ImGroup] {
|
||||
let config = XuqmSDK.shared.requireConfig()
|
||||
let groups: [ImGroup] = try await ApiClient.shared.request(
|
||||
|
||||
@ -43,6 +43,7 @@ public struct ImMessage: Codable, Sendable {
|
||||
public let status: MsgStatus
|
||||
public let mentionedUserIds: String?
|
||||
public let groupReadCount: Int?
|
||||
public let revoked: Bool?
|
||||
public let createdAt: Int64
|
||||
}
|
||||
|
||||
@ -63,9 +64,14 @@ public protocol ImEventDelegate: AnyObject {
|
||||
func imClientDidDisconnect(reason: String?)
|
||||
func imClientDidReceiveMessage(_ message: ImMessage)
|
||||
func imClientDidReceiveGroupMessage(_ message: ImMessage)
|
||||
func imClientDidReceiveRevokedMessage(_ message: ImMessage)
|
||||
func imClientDidError(_ error: String)
|
||||
}
|
||||
|
||||
public extension ImEventDelegate {
|
||||
func imClientDidReceiveRevokedMessage(_ message: ImMessage) {}
|
||||
}
|
||||
|
||||
public struct ImGroup: Codable, Sendable {
|
||||
public let id: String
|
||||
public let appId: String
|
||||
@ -139,6 +145,10 @@ public struct ImSendMessageRequest: Encodable, Sendable {
|
||||
public let mentionedUserIds: String?
|
||||
}
|
||||
|
||||
public struct EditMessageRequest: Encodable, Sendable {
|
||||
public let content: String
|
||||
}
|
||||
|
||||
public struct ImLoginResponse: Decodable, Sendable {
|
||||
public let token: String
|
||||
}
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户