152 lines
5.3 KiB
Swift
152 lines
5.3 KiB
Swift
//
|
||
// 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 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 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
|
||
}()
|
||
|
||
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()
|
||
)
|
||
}
|
||
}
|