提交
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
//
|
||||
// FloatWindowViewModel.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/2/27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TUICallEngine
|
||||
|
||||
class FloatingWindowViewModel {
|
||||
|
||||
let callStatusObserver = Observer()
|
||||
let mediaTypeObserver = Observer()
|
||||
let remoteUserListObserver = Observer()
|
||||
let timeCountObserver = Observer()
|
||||
let sceneObserver = Observer()
|
||||
let selfPlayoutVolumeObserver = Observer()
|
||||
let remotePlayoutVolumeObserver = Observer()
|
||||
|
||||
let callTime: Observable<Int> = Observable(0)
|
||||
let mediaType: Observable<TUICallMediaType> = Observable(.unknown)
|
||||
let selfCallStatus: Observable<TUICallStatus> = Observable(.none)
|
||||
let remoteUserList: Observable<[User]> = Observable(Array())
|
||||
let selfUser: Observable<User> = Observable(User())
|
||||
let scene: Observable<TUICallScene> = Observable(TUICallScene.single)
|
||||
let currentSpeakUser: Observable<User> = Observable(User())
|
||||
|
||||
init() {
|
||||
callTime.value = TUICallState.instance.timeCount.value
|
||||
selfCallStatus.value = TUICallState.instance.selfUser.value.callStatus.value
|
||||
mediaType.value = TUICallState.instance.mediaType.value
|
||||
remoteUserList.value = TUICallState.instance.remoteUserList.value
|
||||
selfUser.value = TUICallState.instance.selfUser.value
|
||||
scene.value = TUICallState.instance.scene.value
|
||||
|
||||
registerObserve()
|
||||
}
|
||||
|
||||
deinit {
|
||||
TUICallState.instance.selfUser.value.callStatus.removeObserver(callStatusObserver)
|
||||
TUICallState.instance.mediaType.removeObserver(mediaTypeObserver)
|
||||
TUICallState.instance.remoteUserList.removeObserver(remoteUserListObserver)
|
||||
TUICallState.instance.timeCount.removeObserver(timeCountObserver)
|
||||
TUICallState.instance.scene.removeObserver(sceneObserver)
|
||||
TUICallState.instance.selfUser.value.playoutVolume.removeObserver(selfPlayoutVolumeObserver)
|
||||
|
||||
for index in 0..<TUICallState.instance.remoteUserList.value.count {
|
||||
guard index < TUICallState.instance.remoteUserList.value.count else {
|
||||
break
|
||||
}
|
||||
TUICallState.instance.remoteUserList.value[index].playoutVolume.removeObserver(remotePlayoutVolumeObserver)
|
||||
}
|
||||
}
|
||||
|
||||
func registerObserve() {
|
||||
registerCallStatusObserver()
|
||||
registerMediaTypeObserver()
|
||||
registerRemoteUserListObserver()
|
||||
registerTimeCountObserver()
|
||||
registerSceneObserver()
|
||||
registerVolumeObserver()
|
||||
}
|
||||
|
||||
func registerCallStatusObserver() {
|
||||
TUICallState.instance.selfUser.value.callStatus.addObserver(callStatusObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.selfCallStatus.value = newValue
|
||||
})
|
||||
}
|
||||
|
||||
func registerMediaTypeObserver() {
|
||||
TUICallState.instance.mediaType.addObserver(mediaTypeObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.mediaType.value = newValue
|
||||
})
|
||||
}
|
||||
|
||||
func registerRemoteUserListObserver() {
|
||||
TUICallState.instance.remoteUserList.addObserver(remoteUserListObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.remoteUserList.value = newValue
|
||||
|
||||
if !newValue.contains(where: { $0.id.value == self.currentSpeakUser.value.id.value }) {
|
||||
self.updateCurrentSpeakUser(user: TUICallState.instance.selfUser.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func registerTimeCountObserver() {
|
||||
TUICallState.instance.timeCount.addObserver(timeCountObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.callTime.value = newValue
|
||||
})
|
||||
}
|
||||
|
||||
func registerSceneObserver() {
|
||||
TUICallState.instance.scene.addObserver(sceneObserver) { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.scene.value = newValue
|
||||
}
|
||||
}
|
||||
|
||||
func registerVolumeObserver() {
|
||||
TUICallState.instance.selfUser.value.playoutVolume.addObserver(selfPlayoutVolumeObserver) { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
if newValue > 30 {
|
||||
self.updateCurrentSpeakUser(user: TUICallState.instance.selfUser.value)
|
||||
}
|
||||
}
|
||||
|
||||
for index in 0..<TUICallState.instance.remoteUserList.value.count {
|
||||
guard index < TUICallState.instance.remoteUserList.value.count else {
|
||||
break
|
||||
}
|
||||
let user = TUICallState.instance.remoteUserList.value[index]
|
||||
TUICallState.instance.remoteUserList.value[index].playoutVolume.addObserver(remotePlayoutVolumeObserver) { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
if newValue > 30 {
|
||||
self.updateCurrentSpeakUser(user: user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateCurrentSpeakUser(user: User) {
|
||||
if user.id.value.count > 0 && currentSpeakUser.value.id.value != user.id.value {
|
||||
self.currentSpeakUser.value = user
|
||||
}
|
||||
}
|
||||
|
||||
func getCallTimeString() -> String {
|
||||
return GCDTimer.secondToHMSString(second: callTime.value)
|
||||
}
|
||||
|
||||
func startRemoteView(user: User, videoView: TUIVideoView){
|
||||
CallEngineManager.instance.startRemoteView(user: user, videoView: videoView)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// SelectGroupMemberViewModel.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/5/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ImSDK_Plus
|
||||
|
||||
class SelectGroupMemberViewModel {
|
||||
var selfUser: Observable<User> = Observable(User())
|
||||
var groupMemberList: Observable<[User]> = Observable([])
|
||||
var groupMemberState: [String: Bool] = [:]
|
||||
var groupMemberStateOrigin: [String: Bool] = [:]
|
||||
|
||||
init() {
|
||||
updateUserState()
|
||||
}
|
||||
|
||||
func updateUserState() {
|
||||
selfUser.value = TUICallState.instance.selfUser.value
|
||||
V2TIMManager.sharedInstance().getGroupMemberList(TUICallState.instance.groupId.value,
|
||||
filter: .max,
|
||||
nextSeq: 0) { [weak self] nextSeq, imUserInfoList in
|
||||
guard let self = self else { return }
|
||||
guard let imUserInfoList = imUserInfoList else { return }
|
||||
|
||||
for imUserInfo in imUserInfoList {
|
||||
if imUserInfo.userID == self.selfUser.value.id.value {
|
||||
continue
|
||||
}
|
||||
let user = User.convertUserFromImFullInfo(user: imUserInfo)
|
||||
self.groupMemberList.value.append(user)
|
||||
self.groupMemberState[user.id.value] = false
|
||||
self.groupMemberStateOrigin[user.id.value] = false
|
||||
}
|
||||
|
||||
for user in TUICallState.instance.remoteUserList.value {
|
||||
self.groupMemberState[user.id.value] = true
|
||||
self.groupMemberStateOrigin[user.id.value] = true
|
||||
}
|
||||
|
||||
} fail: { code, message in
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
//
|
||||
// JoinInGroupCallViewModel.swift
|
||||
// TUICallKit-Swift
|
||||
//
|
||||
// Created by noah on 2024/1/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import TUICallEngine
|
||||
import ImSDK_Plus
|
||||
import TUICore
|
||||
|
||||
class JoinInGroupCallViewModel: NSObject, V2TIMGroupListener, JoinInGroupCallViewDelegate {
|
||||
|
||||
private let callStatusObserver = Observer()
|
||||
private var joinGroupCallView = JoinInGroupCallView()
|
||||
private var roomId = TUIRoomId()
|
||||
private var groupId: String = ""
|
||||
private var callMediaType: TUICallMediaType = .unknown
|
||||
private var recordExpansionStatus: Bool = false
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
V2TIMManager.sharedInstance().addGroupListener(listener: self)
|
||||
registerCallStatusObserver()
|
||||
}
|
||||
|
||||
deinit {
|
||||
TUICallState.instance.selfUser.value.callStatus.removeObserver(callStatusObserver)
|
||||
}
|
||||
|
||||
func getGroupAttributes(_ groupID: String) {
|
||||
groupId = groupID
|
||||
recordExpansionStatus = false
|
||||
guard TUICallState.instance.selfUser.value.callStatus.value == .none else {
|
||||
return
|
||||
}
|
||||
V2TIMManager.sharedInstance().getGroupAttributes(groupID, keys: nil) { [weak self] groupAttributeList in
|
||||
guard let self = self, let attributeList = groupAttributeList as? [String : String] else {
|
||||
return
|
||||
}
|
||||
self.processGroupAttributeData(attributeList)
|
||||
} fail: { code, message in
|
||||
}
|
||||
}
|
||||
|
||||
func setJoinGroupCallView(_ joinGroupView: JoinInGroupCallView) {
|
||||
joinGroupCallView = joinGroupView
|
||||
joinGroupCallView.delegate = self
|
||||
joinGroupCallView.isHidden = true
|
||||
}
|
||||
|
||||
func registerCallStatusObserver() {
|
||||
TUICallState.instance.selfUser.value.callStatus.addObserver(callStatusObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
if newValue == .none && !self.groupId.isEmpty && self.groupId.count > 0 {
|
||||
self.getGroupAttributes(self.groupId)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: - Private Method
|
||||
private func processGroupAttributeData(_ groupAttributeList: [String: String]) {
|
||||
guard let jsonStr = groupAttributeList["inner_attr_kit_info"], jsonStr.count > 0,
|
||||
let jsonData = jsonStr.data(using: .utf8),
|
||||
let groupAttributeDic = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any],
|
||||
checkBusinessType(groupAttributeDic) else {
|
||||
hiddenJoinGroupCallView()
|
||||
return
|
||||
}
|
||||
|
||||
handleRoomId(groupAttributeDic)
|
||||
handleCallMediaType(groupAttributeDic)
|
||||
|
||||
guard let userIdList = getUserIdList(groupAttributeDic), userIdList.count > 0 else {
|
||||
hiddenJoinGroupCallView()
|
||||
return
|
||||
}
|
||||
|
||||
if userIdList.contains(TUICallState.instance.selfUser.value.id.value) {
|
||||
hiddenJoinGroupCallView()
|
||||
return
|
||||
}
|
||||
|
||||
handleUsersInfo(userIdList)
|
||||
}
|
||||
|
||||
private func checkBusinessType(_ groupAttributeValue: [String: Any]) -> Bool {
|
||||
guard let businessType = groupAttributeValue["business_type"] as? String else {
|
||||
return false
|
||||
}
|
||||
return businessType == "callkit"
|
||||
}
|
||||
|
||||
private func handleRoomId(_ groupAttributeValue: [String: Any]) {
|
||||
guard let strRoomId = groupAttributeValue["room_id"] as? String, strRoomId.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
if groupAttributeValue["room_id_type"] as? Int == 2 {
|
||||
roomId.strRoomId = strRoomId
|
||||
return
|
||||
}
|
||||
|
||||
if let intRoomId = UInt32(strRoomId) {
|
||||
roomId.intRoomId = intRoomId
|
||||
} else {
|
||||
roomId.strRoomId = strRoomId
|
||||
}
|
||||
}
|
||||
|
||||
private func handleCallMediaType(_ groupAttributeValue: [String: Any]) {
|
||||
guard let callMediaType = groupAttributeValue["call_media_type"] as? String, callMediaType.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
if callMediaType == "audio" {
|
||||
self.callMediaType = TUICallMediaType.audio
|
||||
} else if callMediaType == "video" {
|
||||
self.callMediaType = TUICallMediaType.video
|
||||
}
|
||||
}
|
||||
|
||||
private func getUserIdList(_ groupAttributeValue: [String: Any]) -> [String]? {
|
||||
guard let userInfoList = groupAttributeValue["user_list"] as? [[String: Any]] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var userIdList = [String]()
|
||||
for userInfoDic in userInfoList {
|
||||
guard let userId = userInfoDic["userid"] as? String else {
|
||||
break
|
||||
}
|
||||
userIdList.append(userId)
|
||||
}
|
||||
|
||||
return userIdList
|
||||
}
|
||||
|
||||
private func handleUsersInfo(_ userIdList: [String]) {
|
||||
V2TIMManager.sharedInstance().getUsersInfo(userIdList) { [weak self] infoList in
|
||||
guard let self = self, let userInfoList = infoList else { return }
|
||||
|
||||
var userModelList: [User] = Array()
|
||||
|
||||
for userInfo in userInfoList {
|
||||
let userModel = User()
|
||||
userModel.id.value = userInfo.userID ?? ""
|
||||
userModel.avatar.value = userInfo.faceURL ?? ""
|
||||
userModelList.append(userModel)
|
||||
}
|
||||
|
||||
if userModelList.count > 1 {
|
||||
self.showJoinGroupCallView()
|
||||
self.joinGroupCallView.updateView(with: userModelList, callMediaType:self.callMediaType)
|
||||
} else {
|
||||
self.hiddenJoinGroupCallView()
|
||||
}
|
||||
} fail: { code, message in
|
||||
}
|
||||
}
|
||||
|
||||
private func showJoinGroupCallView() {
|
||||
joinGroupCallView.isHidden = false
|
||||
updatePageContent()
|
||||
postUpdateNotification()
|
||||
}
|
||||
|
||||
func hiddenJoinGroupCallView() {
|
||||
joinGroupCallView.isHidden = true
|
||||
if let parentView = joinGroupCallView.superview {
|
||||
parentView.frame = CGRect(x: 0, y: 0, width: parentView.bounds.size.width, height: 0)
|
||||
postUpdateNotification()
|
||||
}
|
||||
}
|
||||
|
||||
func updatePageContent() {
|
||||
DispatchQueue.main.async {
|
||||
let joinGroupCallViewHeight = self.recordExpansionStatus ? kJoinGroupCallViewExpandHeight : kJoinGroupCallViewDefaultHeight
|
||||
self.joinGroupCallView.frame = CGRect(x: self.joinGroupCallView.frame.origin.x,
|
||||
y: self.joinGroupCallView.frame.origin.y,
|
||||
width: self.joinGroupCallView.bounds.size.width,
|
||||
height: joinGroupCallViewHeight)
|
||||
if let parentView = self.joinGroupCallView.superview {
|
||||
parentView.frame = CGRect(x: 0,
|
||||
y: 0,
|
||||
width: parentView.bounds.size.width,
|
||||
height: self.joinGroupCallView.bounds.size.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func postUpdateNotification() {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: Notification.Name(TUICore_TUIChatExtension_ChatViewTopArea_ChangedNotification), object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - TUICallKitJoinGroupCallViewDelegate
|
||||
func updatePageContent(isExpand: Bool) {
|
||||
if recordExpansionStatus != isExpand {
|
||||
recordExpansionStatus = isExpand
|
||||
}
|
||||
updatePageContent()
|
||||
postUpdateNotification()
|
||||
}
|
||||
|
||||
func joinInGroupCall() {
|
||||
hiddenJoinGroupCallView()
|
||||
TUICallKit.createInstance().joinInGroupCall(roomId: roomId, groupId: groupId, callMediaType: callMediaType)
|
||||
}
|
||||
|
||||
// MARK: - V2TIMGroupListener
|
||||
func onGroupAttributeChanged(_ groupID: String!, attributes: NSMutableDictionary!) {
|
||||
guard let attributes = attributes as? [String: String],
|
||||
groupId == groupID else {
|
||||
return
|
||||
}
|
||||
|
||||
if TUICallState.instance.selfUser.value.callStatus.value != .none {
|
||||
self.hiddenJoinGroupCallView()
|
||||
return
|
||||
}
|
||||
|
||||
processGroupAttributeData(attributes)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// TUICallRecordCallsCellViewModel.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/8/28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import TUICallEngine
|
||||
import ImSDK_Plus
|
||||
import TUICore
|
||||
|
||||
class TUICallRecordCallsCellViewModel {
|
||||
|
||||
var avatarImage: UIImage = UIImage()
|
||||
var faceURL: Observable<String> = Observable("")
|
||||
|
||||
var titleLabelStr: Observable<String> = Observable("")
|
||||
var mediaTypeImageStr: String = ""
|
||||
var resultLabelStr: String = ""
|
||||
var timeLabelStr: String = ""
|
||||
|
||||
var callRecord: TUICallRecords
|
||||
|
||||
init(_ record: TUICallRecords) {
|
||||
callRecord = record
|
||||
|
||||
configAvatarImage(callRecord)
|
||||
configResult(callRecord.result)
|
||||
configMediaTypeImageName(callRecord.mediaType)
|
||||
configTitle(callRecord)
|
||||
configTime(callRecord)
|
||||
}
|
||||
|
||||
private func configAvatarImage(_ callRecord: TUICallRecords) {
|
||||
if callRecord.scene == .group {
|
||||
|
||||
var inviteList = callRecord.inviteList
|
||||
inviteList.insert(callRecord.inviter, at: 0)
|
||||
guard let inviteList = inviteList as? [String] else { return }
|
||||
configGroupAvatarImage(inviteList)
|
||||
|
||||
} else if callRecord.scene == .single {
|
||||
configSingleAvatarImage(callRecord)
|
||||
} else {
|
||||
avatarImage = TUICoreDefineConvert.getDefaultGroupAvatarImage()
|
||||
}
|
||||
}
|
||||
|
||||
private func configGroupAvatarImage(_ inviteList: [String]) {
|
||||
if inviteList.isEmpty {
|
||||
avatarImage = TUICoreDefineConvert.getDefaultGroupAvatarImage()
|
||||
return
|
||||
}
|
||||
|
||||
let inviteStr = inviteList.joined(separator: "#")
|
||||
|
||||
getCacheAvatarForInviteStr(inviteStr) { [weak self] avatar in
|
||||
guard let self = self else { return }
|
||||
if let avatar = avatar {
|
||||
self.avatarImage = avatar
|
||||
} else {
|
||||
V2TIMManager.sharedInstance()?.getUsersInfo(inviteList, succ: { infoList in
|
||||
var avatarsList = [String]()
|
||||
|
||||
infoList?.forEach { userFullInfo in
|
||||
if let faceURL = userFullInfo.faceURL, !faceURL.isEmpty {
|
||||
avatarsList.append(faceURL)
|
||||
} else {
|
||||
avatarsList.append("http://placeholder")
|
||||
}
|
||||
}
|
||||
TUIGroupAvatar.createGroupAvatar(avatarsList, finished: { [weak self] image in
|
||||
guard let self = self else { return }
|
||||
self.avatarImage = image
|
||||
self.cacheGroupCallAvatar(image, inviteStr: inviteStr)
|
||||
})
|
||||
}, fail: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func configSingleAvatarImage(_ callRecord: TUICallRecords) {
|
||||
avatarImage = TUICoreDefineConvert.getDefaultAvatarImage()
|
||||
var useId = callRecord.inviter
|
||||
|
||||
if callRecord.role == .call {
|
||||
guard let firstInvite = callRecord.inviteList.first as? String else { return }
|
||||
useId = firstInvite
|
||||
}
|
||||
|
||||
if !useId.isEmpty {
|
||||
V2TIMManager.sharedInstance()?.getUsersInfo([useId], succ: { [weak self] infoList in
|
||||
guard let self = self else { return }
|
||||
if let userFullInfo = infoList?.first {
|
||||
if let faceURL = userFullInfo.faceURL, !faceURL.isEmpty, faceURL.hasPrefix("http") {
|
||||
self.faceURL.value = faceURL
|
||||
}
|
||||
}
|
||||
}, fail: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func configResult(_ callResultType: TUICallResultType) {
|
||||
switch callResultType {
|
||||
case .missed:
|
||||
resultLabelStr = TUICallKitLocalize(key: "TUICallKit.Recents.missed") ?? "Missed"
|
||||
case .incoming:
|
||||
resultLabelStr = TUICallKitLocalize(key: "TUICallKit.Recents.incoming") ?? "Incoming"
|
||||
case .outgoing:
|
||||
resultLabelStr = TUICallKitLocalize(key: "TUICallKit.Recents.outgoing") ?? "Outgoing"
|
||||
case .unknown:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func configMediaTypeImageName(_ callMediaType: TUICallMediaType) {
|
||||
if callMediaType == .audio {
|
||||
mediaTypeImageStr = "ic_recents_audio"
|
||||
} else if callMediaType == .video {
|
||||
mediaTypeImageStr = "ic_recents_video"
|
||||
}
|
||||
}
|
||||
|
||||
func configTitle(_ callRecord: TUICallRecords) {
|
||||
titleLabelStr.value = ""
|
||||
var allUsers: [String] = []
|
||||
|
||||
switch callRecord.scene {
|
||||
case .single:
|
||||
if callRecord.role == .call {
|
||||
guard let inviteList = callRecord.inviteList as? [String] else { return }
|
||||
allUsers = inviteList
|
||||
} else if callRecord.role == .called {
|
||||
let inviter = callRecord.inviter
|
||||
allUsers.append(inviter)
|
||||
}
|
||||
case .group:
|
||||
guard let inviteList = callRecord.inviteList as? [String] else { return }
|
||||
allUsers = inviteList
|
||||
case .multi:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
|
||||
User.getUserInfosFromIM(userIDs: allUsers) { [weak self] infoList in
|
||||
guard let self = self else { return }
|
||||
let titleArray = infoList.map { $0.remark.value.count > 0
|
||||
? $0.remark.value
|
||||
: $0.nickname.value.count > 0 ? $0.nickname.value : $0.id.value }
|
||||
self.titleLabelStr.value = titleArray.joined(separator: ",")
|
||||
}
|
||||
}
|
||||
|
||||
private func configTime(_ callRecord: TUICallRecords) {
|
||||
let beginTime: TimeInterval = callRecord.beginTime / 1_000
|
||||
if beginTime <= 0 {
|
||||
return
|
||||
}
|
||||
timeLabelStr = TUITool.convertDate(toStr: Date(timeIntervalSince1970: beginTime))
|
||||
}
|
||||
|
||||
// MARK: - Cache
|
||||
private func getCacheAvatarForInviteStr(_ inviteStr: String, completion: @escaping (UIImage?) -> Void) {
|
||||
let cacheKey = "group_call_avatar_\(inviteStr)"
|
||||
let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first ?? ""
|
||||
let filePath = "\(cachePath)/\(cacheKey)"
|
||||
let fileManager = FileManager.default
|
||||
|
||||
if fileManager.fileExists(atPath: filePath) {
|
||||
if let data = fileManager.contents(atPath: filePath), let image = UIImage(data: data) {
|
||||
completion(image)
|
||||
return
|
||||
}
|
||||
}
|
||||
completion(nil)
|
||||
}
|
||||
|
||||
private func cacheGroupCallAvatar(_ avatar: UIImage, inviteStr: String) {
|
||||
let cacheKey = "group_call_avatar_\(inviteStr)"
|
||||
let cachePath = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first ?? ""
|
||||
let filePath = "\(cachePath)/\(cacheKey)"
|
||||
let fileManager = FileManager.default
|
||||
|
||||
if let data = avatar.pngData() {
|
||||
fileManager.createFile(atPath: filePath, contents: data, attributes: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
//
|
||||
// TUICallRecordCallsViewModel.swift
|
||||
//
|
||||
//
|
||||
// Created by vincepzhang on 2023/8/28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import TUICallEngine
|
||||
import TUICore
|
||||
|
||||
enum TUICallRecordCallsType: Int {
|
||||
case all
|
||||
case missed
|
||||
}
|
||||
|
||||
enum TUICallKitRecordCallsUIStyle: Int {
|
||||
case classic
|
||||
case minimalist
|
||||
}
|
||||
|
||||
class TUICallRecordCallsViewModel {
|
||||
|
||||
var dataSource: Observable<[TUICallRecordCallsCellViewModel]> = Observable(Array())
|
||||
var allDataSource: [TUICallRecordCallsCellViewModel] = []
|
||||
var missedDataSource: [TUICallRecordCallsCellViewModel] = []
|
||||
|
||||
var recordCallsUIStyle: TUICallKitRecordCallsUIStyle = .minimalist
|
||||
var recordCallsType: TUICallRecordCallsType = .all
|
||||
|
||||
typealias SuccClosureType = @convention(block) (UIViewController) -> Void
|
||||
typealias FailClosureType = @convention(block) (Int, String) -> Void
|
||||
|
||||
func queryRecentCalls() {
|
||||
let filter = TUICallRecentCallsFilter()
|
||||
TUICallEngine.createInstance().queryRecentCalls(filter: filter, succ: { [weak self] callRecords in
|
||||
guard let self = self else { return }
|
||||
var viewModelList: [TUICallRecordCallsCellViewModel] = []
|
||||
callRecords.forEach { callRecord in
|
||||
let viewModel = TUICallRecordCallsCellViewModel(callRecord)
|
||||
viewModelList.append(viewModel)
|
||||
}
|
||||
self.updateDataSource(viewModelList)
|
||||
}, fail: {})
|
||||
}
|
||||
|
||||
func updateDataSource(_ viewModelList: [TUICallRecordCallsCellViewModel]) {
|
||||
if viewModelList.isEmpty {
|
||||
return
|
||||
}
|
||||
cleanAllSource()
|
||||
allDataSource = viewModelList
|
||||
viewModelList.forEach { viewModel in
|
||||
if viewModel.callRecord.result == .missed {
|
||||
missedDataSource.append(viewModel)
|
||||
}
|
||||
}
|
||||
reloadDataSource()
|
||||
}
|
||||
|
||||
func switchRecordCallsType(_ type: TUICallRecordCallsType) {
|
||||
recordCallsType = type
|
||||
reloadDataSource()
|
||||
}
|
||||
|
||||
func reloadDataSource() {
|
||||
switch recordCallsType {
|
||||
case .all:
|
||||
dataSource.value = allDataSource
|
||||
case .missed:
|
||||
dataSource.value = missedDataSource
|
||||
}
|
||||
}
|
||||
|
||||
func cleanAllSource() {
|
||||
dataSource.value.removeAll()
|
||||
allDataSource.removeAll()
|
||||
missedDataSource.removeAll()
|
||||
}
|
||||
|
||||
func cleanSource(viewModel: TUICallRecordCallsCellViewModel) {
|
||||
allDataSource.removeAll() { $0.callRecord.callId == viewModel.callRecord.callId }
|
||||
missedDataSource.removeAll() { $0.callRecord.callId == viewModel.callRecord.callId }
|
||||
}
|
||||
|
||||
func cleanSource(callId: String) {
|
||||
allDataSource.removeAll() { $0.callRecord.callId == callId }
|
||||
missedDataSource.removeAll() { $0.callRecord.callId == callId }
|
||||
}
|
||||
|
||||
|
||||
func repeatCall(_ indexPath: IndexPath) {
|
||||
let cellViewModel = dataSource.value[indexPath.row]
|
||||
|
||||
if cellViewModel.callRecord.scene == .single {
|
||||
repeatSingleCall(cellViewModel.callRecord)
|
||||
}
|
||||
}
|
||||
|
||||
func repeatSingleCall(_ callRecord: TUICallRecords) {
|
||||
var userId = callRecord.inviteList.first
|
||||
if callRecord.role == .called {
|
||||
userId = callRecord.inviter
|
||||
}
|
||||
|
||||
guard let userId = userId as? String else { return }
|
||||
|
||||
TUICallKit.createInstance().call(userId: userId, callMediaType: callRecord.mediaType)
|
||||
}
|
||||
|
||||
func deleteAllRecordCalls() {
|
||||
var callIdList: [String] = []
|
||||
|
||||
if recordCallsType == .all {
|
||||
callIdList = getCallIdList(allDataSource)
|
||||
} else if recordCallsType == .missed {
|
||||
callIdList = getCallIdList(missedDataSource)
|
||||
}
|
||||
|
||||
TUICallEngine.createInstance().deleteRecordCalls(callIdList, succ: { [weak self] succList in
|
||||
guard let self = self else { return }
|
||||
|
||||
succList.forEach { callId in
|
||||
self.cleanSource(callId: callId)
|
||||
}
|
||||
|
||||
self.reloadDataSource()
|
||||
}, fail: {})
|
||||
}
|
||||
|
||||
func getCallIdList(_ cellViewModelArray: [TUICallRecordCallsCellViewModel]) -> [String] {
|
||||
var callIdList: [String] = []
|
||||
|
||||
if cellViewModelArray.isEmpty {
|
||||
return callIdList
|
||||
}
|
||||
|
||||
cellViewModelArray.forEach { obj in
|
||||
callIdList.append(obj.callRecord.callId)
|
||||
}
|
||||
|
||||
return callIdList
|
||||
}
|
||||
|
||||
func deleteRecordCall(_ indexPath: IndexPath) {
|
||||
if indexPath.row < 0 || indexPath.row >= dataSource.value.count {
|
||||
return
|
||||
}
|
||||
let viewModel = dataSource.value[indexPath.row]
|
||||
|
||||
TUICallEngine.createInstance().deleteRecordCalls([viewModel.callRecord.callId], succ: { [weak self] _ in
|
||||
guard let self = self else { return }
|
||||
self.cleanSource(viewModel: viewModel)
|
||||
self.reloadDataSource()
|
||||
|
||||
}, fail: {})
|
||||
}
|
||||
|
||||
func jumpUserInfoController(indexPath: IndexPath, navigationController: UINavigationController) {
|
||||
if indexPath.row < 0 || indexPath.row >= dataSource.value.count {
|
||||
return
|
||||
}
|
||||
let cellViewModel = dataSource.value[indexPath.row]
|
||||
|
||||
let groupId = cellViewModel.callRecord.groupId
|
||||
var userId = cellViewModel.callRecord.inviter
|
||||
|
||||
if cellViewModel.callRecord.role == .call {
|
||||
guard let firstUserId = cellViewModel.callRecord.inviteList.first as? String else { return }
|
||||
userId = firstUserId
|
||||
}
|
||||
|
||||
if !groupId.isEmpty {
|
||||
let param: [String: Any] = [TUICore_TUIContactObjectFactory_GetGroupInfoVC_GroupID: groupId]
|
||||
if TUICallKitRecordCallsUIStyle.classic == recordCallsUIStyle {
|
||||
navigationController.push(TUICore_TUIContactObjectFactory_GetGroupInfoVC_Classic, param: param, forResult: nil)
|
||||
} else {
|
||||
navigationController.push(TUICore_TUIContactObjectFactory_GetGroupInfoVC_Minimalist, param: param, forResult: nil)
|
||||
}
|
||||
} else if !userId.isEmpty {
|
||||
getUserOrFriendProfileVCWithUserID(userId: userId) { viewController in
|
||||
navigationController.pushViewController(viewController, animated: true)
|
||||
} fail: { code, desc in
|
||||
TUITool.makeToastError(Int(code), msg: desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getUserOrFriendProfileVCWithUserID(userId: String, succ: @escaping SuccClosureType, fail: @escaping FailClosureType) {
|
||||
let param: NSDictionary = [
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_UserIDKey: userId,
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_SuccKey: succ,
|
||||
TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod_FailKey: fail,
|
||||
]
|
||||
|
||||
if TUICallKitRecordCallsUIStyle.classic == self.recordCallsUIStyle {
|
||||
TUICore.createObject(TUICore_TUIContactObjectFactory,
|
||||
key: TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod, param: param as? [AnyHashable : Any])
|
||||
} else {
|
||||
TUICore.createObject(TUICore_TUIContactObjectFactory_Minimalist,
|
||||
key: TUICore_TUIContactObjectFactory_GetUserOrFriendProfileVCMethod, param: param as? [AnyHashable : Any])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// GroupCallVideoCellViewModel.swift
|
||||
// TUICallKit
|
||||
//
|
||||
// Created by vincepzhang on 2023/3/6.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import TUICallEngine
|
||||
|
||||
class GroupCallVideoCellViewModel {
|
||||
|
||||
let showLargeViewUserIdObserver = Observer()
|
||||
let remoteUserStatusObserver = Observer()
|
||||
let remoteUserVideoAvailableObserver = Observer()
|
||||
let remotePlayoutVolumeObserver = Observer()
|
||||
let remoteNetworkQualityObserver = Observer()
|
||||
|
||||
var remoteUser = User()
|
||||
let isShowLargeViewUserId: Observable<Bool> = Observable(false)
|
||||
let remoteUserStatus: Observable<TUICallStatus> = Observable(.none)
|
||||
let remoteUserVideoAvailable: Observable<Bool> = Observable(false)
|
||||
let remoteUserVolume: Observable<Float> = Observable(0)
|
||||
let remoteIsShowLowNetworkQuality: Observable<Bool> = Observable(false)
|
||||
|
||||
init(remote: User) {
|
||||
remoteUser = remote
|
||||
isShowLargeViewUserId.value = (TUICallState.instance.showLargeViewUserId.value == remote.id.value) && (remote.id.value.count > 0)
|
||||
registerObserve()
|
||||
}
|
||||
|
||||
deinit {
|
||||
TUICallState.instance.showLargeViewUserId.removeObserver(showLargeViewUserIdObserver)
|
||||
|
||||
for index in 0..<TUICallState.instance.remoteUserList.value.count {
|
||||
guard index < TUICallState.instance.remoteUserList.value.count else {
|
||||
break
|
||||
}
|
||||
if TUICallState.instance.remoteUserList.value[index].id.value == remoteUser.id.value {
|
||||
TUICallState.instance.remoteUserList.value[index].callStatus.removeObserver(remoteUserStatusObserver)
|
||||
TUICallState.instance.remoteUserList.value[index].videoAvailable.removeObserver(remoteUserVideoAvailableObserver)
|
||||
TUICallState.instance.remoteUserList.value[index].networkQualityReminder.removeObserver(remoteNetworkQualityObserver)
|
||||
TUICallState.instance.remoteUserList.value[index].playoutVolume.removeObserver(remotePlayoutVolumeObserver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerObserve() {
|
||||
TUICallState.instance.showLargeViewUserId.addObserver(showLargeViewUserIdObserver, closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.isShowLargeViewUserId.value = (newValue == self.remoteUser.id.value)
|
||||
})
|
||||
|
||||
for index in 0..<TUICallState.instance.remoteUserList.value.count {
|
||||
guard index < TUICallState.instance.remoteUserList.value.count else {
|
||||
break
|
||||
}
|
||||
|
||||
if TUICallState.instance.remoteUserList.value[index].id.value == remoteUser.id.value {
|
||||
|
||||
remoteUserStatus.value = TUICallState.instance.remoteUserList.value[index].callStatus.value
|
||||
remoteUserVideoAvailable.value = TUICallState.instance.remoteUserList.value[index].videoAvailable.value
|
||||
|
||||
TUICallState.instance.remoteUserList.value[index].callStatus.addObserver(remoteUserStatusObserver,
|
||||
closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.remoteUserStatus.value = newValue
|
||||
})
|
||||
|
||||
TUICallState.instance.remoteUserList.value[index].videoAvailable.addObserver(remoteUserVideoAvailableObserver,
|
||||
closure: { [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.remoteUserVideoAvailable.value = newValue
|
||||
})
|
||||
|
||||
TUICallState.instance.remoteUserList.value[index].networkQualityReminder.addObserver(remoteNetworkQualityObserver)
|
||||
{ [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.remoteIsShowLowNetworkQuality.value = newValue
|
||||
}
|
||||
|
||||
TUICallState.instance.remoteUserList.value[index].playoutVolume.addObserver(remotePlayoutVolumeObserver)
|
||||
{ [weak self] newValue, _ in
|
||||
guard let self = self else { return }
|
||||
self.remoteUserVolume.value = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user