jsdw_ios/QuickLocation/Section/Group/GroupViewModel.swift

167 lines
5.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// GroupViewModel.swift
// QuickLocation
//
import Foundation
import RxSwift
import RxCocoa
import RxDataSources
import OpenIMSDK
import ObjectMapper
typealias HotGroupListSectionModel = SectionModel<String, GroupInfoModel>
typealias CircleListSectionModel = SectionModel<String, GroupCellData>
/// 群聊 cell 数据:圈子集合以业务 `groups` 为准,IM 会话只补未读/预览/时间
struct GroupCellData {
let group: GroupInfoModel
let unreadCount: Int
let lastMsgTime: Int // ms timestamp
let lastMsgPreview: String
var tags: [String] { Array(group.labels.prefix(2)) }
var iconURL: String { group.groupIconURL }
var localIcon: UIImage { group.groupIcon }
}
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: ["我创建的圈子", "我加入的圈子"])
private(set) var groupList: [GroupInfoModel] = []
private var cachedConversations: [OIMConversationInfo] = []
/// 热门酷圈
func loadHotGroupData(_ list: [GroupInfoModel]) {
hotGroupSectionedItems.onNext(list.mapSection())
}
lazy var hotGroupCellAction: Action<GroupInfoModel, Void> = { this in
return Action { model in
AppRouter.push(Route.groupInfo, userInfo: ["groupInfo": model.toJSON()])
return .empty()
}
}(self)
/// 业务圈子列表(与首页切圈弹层同一份 `groups`)
func applyBusinessGroups(_ list: [GroupInfoModel]) {
groupList = list
reloadData()
}
/// 全量刷新 IM 会话(未读、最后一条、时间)
func loadConversations(_ conversations: [OIMConversationInfo]) {
cachedConversations = conversations
reloadData()
}
/// 仅更新会话元数据(未读、时间)- 消息监听回调(只返回变更的会话,需合并)
func updateConversations(_ conversations: [OIMConversationInfo]) {
var merged: [String: OIMConversationInfo] = [:]
for c in cachedConversations {
if let key = Self.conversationGroupKey(c) { merged[key] = c }
}
for c in conversations {
if let key = Self.conversationGroupKey(c) { merged[key] = c }
}
cachedConversations = Array(merged.values)
reloadData()
}
private static func conversationGroupKey(_ conversation: OIMConversationInfo) -> String? {
if let groupID = conversation.groupID, !groupID.isEmpty {
return groupID
}
guard let conversationID = conversation.conversationID, conversationID.hasPrefix("sg_"),
conversationID.count > 3 else { return nil }
return String(conversationID.dropFirst(3))
}
private func reloadData() {
let convMap: [String: OIMConversationInfo] = {
var m: [String: OIMConversationInfo] = [:]
for c in cachedConversations {
if let key = Self.conversationGroupKey(c) {
m[key] = c
}
}
return m
}()
func cellData(for group: GroupInfoModel) -> GroupCellData {
let conv = convMap[group.group_key]
return GroupCellData(
group: group,
unreadCount: Int(conv?.unreadCount ?? 0),
lastMsgTime: Int(conv?.latestMsgSendTime ?? 0),
lastMsgPreview: Self.previewText(from: conv?.latestMsg)
)
}
func sortedByLastMsg(_ items: [GroupCellData]) -> [GroupCellData] {
items.sorted { lhs, rhs in
if lhs.lastMsgTime != rhs.lastMsgTime {
return lhs.lastMsgTime > rhs.lastMsgTime
}
return lhs.group.group_key < rhs.group.group_key
}
}
let all = sortedByLastMsg(groupList.map(cellData(for:)))
let created = sortedByLastMsg(groupList.filter(\.is_owner).map(cellData(for:)))
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()
)
}
}