增加换肤功能

This commit is contained in:
启星
2025-08-14 10:07:49 +08:00
parent f6964c1e89
commit 4f9318d98e
8789 changed files with 978530 additions and 2 deletions

View File

@@ -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)
}
}
}

View File

@@ -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])
}
}
}