// // GroupViewModel.swift // QuickLocation // import Foundation import RxSwift import RxCocoa import RxDataSources import OpenIMSDK import ObjectMapper typealias HotGroupListSectionModel = SectionModel typealias CircleListSectionModel = SectionModel /// 群聊 cell 数据(融合群信息 + 会话信息) struct GroupCellData { let groupInfo: OIMGroupInfo let unreadCount: Int let lastMsgTime: Int // ms timestamp let lastMsgPreview: String let tags: [String] let localIcon: UIImage } final class GroupViewModel { struct Output { let hotGroups: Observable<[HotGroupListSectionModel]> let createdSections: Observable<[CircleListSectionModel]> let joinedSections: Observable<[CircleListSectionModel]> let segmentTitles: Driver<[String]> } let output: Output private let hotGroupSectionedItems = PublishSubject<[HotGroupListSectionModel]>() private let createdSectionsSubject = PublishSubject<[CircleListSectionModel]>() private let joinedSectionsSubject = PublishSubject<[CircleListSectionModel]>() let segmentTitles = BehaviorRelay<[String]>(value: ["我创建的圈子", "我加入的圈子"]) var groupList: [GroupInfoModel] = [] private var cachedGroups: [OIMGroupInfo] = [] private var cachedConversations: [OIMConversationInfo] = [] /// 当前群的本地图标 func getLocalIcon(groupId: String) -> UIImage { groupList.first { groupId == $0.group_key }?.groupIcon ?? UIImage(named: "GroupIcon/1")! } /// 热门酷圈 func loadHotGroupData(_ list: [GroupInfoModel]) { hotGroupSectionedItems.onNext(list.mapSection()) } lazy var hotGroupCellAction: Action = { this in return Action { model in AppRouter.push(Route.groupInfo, userInfo: ["groupInfo": model.toJSON()]) return .empty() } }(self) /// 加载群列表(全量刷新) func loadIMGroups(_ groups: [OIMGroupInfo], conversations: [OIMConversationInfo] = []) { if !groups.isEmpty { cachedGroups = groups } if !conversations.isEmpty { cachedConversations = conversations } reloadData() } /// 仅更新会话元数据(未读、时间)- 消息监听回调(只返回变更的会话,需合并) func updateConversations(_ conversations: [OIMConversationInfo]) { var merged: [String: OIMConversationInfo] = [:] for c in cachedConversations { if let gid = c.groupID { merged[gid] = c } } for c in conversations { if let gid = c.groupID { merged[gid] = c } } cachedConversations = Array(merged.values) reloadData() } private func reloadData() { let currentUserId = AppContextManager.shared.userId let convMap: [String: OIMConversationInfo] = { var m: [String: OIMConversationInfo] = [:] for c in cachedConversations where c.groupID != nil && !(c.groupID?.isEmpty ?? true) { m[c.groupID!] = c } return m }() // 「全部圈子」= 全部已加入群;「我的圈子」= 我创建的 var all: [GroupCellData] = [] var created: [GroupCellData] = [] for g in cachedGroups { let conv = convMap[g.groupID ?? ""] let data = GroupCellData( groupInfo: g, unreadCount: Int(conv?.unreadCount ?? 0), lastMsgTime: Int(conv?.latestMsgSendTime ?? 0), lastMsgPreview: Self.previewText(from: conv?.latestMsg), tags: Array((groupList.first { $0.group_key == g.groupID }?.labels ?? []).prefix(2)), localIcon: getLocalIcon(groupId: g.groupID ?? "") ) all.append(data) if g.ownerUserID == currentUserId || g.creatorUserID == currentUserId { created.append(data) } } createdSectionsSubject.onNext(created.mapSection()) joinedSectionsSubject.onNext(all.mapSection()) } private static func previewText(from msg: OIMMessageInfo?) -> String { guard let msg else { return "" } let body: String switch msg.contentType { case .image: body = "[图片]" case .audio: body = "[语音]" case .video: body = "[视频]" case .file: body = "[文件]" case .location: body = "[位置]" case .at: body = Self.displayBody(msg.atTextElem?.text ?? "[消息]") case .quote: body = Self.displayBody(msg.quoteElem?.text ?? "[消息]") case .face: body = "[表情]" default: body = Self.displayBody(msg.textElem?.content ?? msg.content ?? "") } let name = msg.senderNickname ?? "" if name.isEmpty { return body } if body.isEmpty { return name } return "\(name): \(body)" } private static func displayBody(_ text: String) -> String { if text.hasPrefix("js_emoji:") { return "[表情]" } return text } // MARK: - Init init() { output = Output( hotGroups: hotGroupSectionedItems.asObservable(), createdSections: createdSectionsSubject.asObservable(), joinedSections: joinedSectionsSubject.asObservable(), segmentTitles: segmentTitles.asDriver() ) } }