188 lines
6.1 KiB
Swift
188 lines
6.1 KiB
Swift
|
|
//
|
|||
|
|
// SessionBaseModel.swift
|
|||
|
|
// Crush
|
|||
|
|
//
|
|||
|
|
// Created by Leon on 2025/8/18.
|
|||
|
|
//
|
|||
|
|
import UIKit
|
|||
|
|
import Foundation
|
|||
|
|
import NIMSDK
|
|||
|
|
protocol SessionCellContentDelegate {
|
|||
|
|
/// contentView 高度
|
|||
|
|
func contentSize(model: SessionBaseModel) -> CGSize
|
|||
|
|
/// 气泡在cell中的边距(外)
|
|||
|
|
func cellInsets(model: SessionBaseModel) -> UIEdgeInsets
|
|||
|
|
/// contentView距离在气泡内距离四周的边距(bubble内)
|
|||
|
|
func contentInsets(model: SessionBaseModel) -> UIEdgeInsets
|
|||
|
|
/// 是否只显示contentView, 默认false。 true时,局中显示
|
|||
|
|
func onlyShowContent(model: SessionBaseModel) -> Bool
|
|||
|
|
/// 获取当前conten View的类名
|
|||
|
|
func contentViewClass(model: SessionBaseModel) -> IMContentBaseView.Type
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum SessionCellType: String, Codable {
|
|||
|
|
case unknown
|
|||
|
|
case text // 普通文本消息
|
|||
|
|
case aimsg // AI Msg
|
|||
|
|
case image // 图片
|
|||
|
|
case timeStamp // 时间戳
|
|||
|
|
case create // 创建关系
|
|||
|
|
case keyword // 敏感词
|
|||
|
|
case tips // 提示
|
|||
|
|
case gift // 送礼
|
|||
|
|
case phonecall // im phone call
|
|||
|
|
case aiLoading
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class SessionBaseModel: CustomStringConvertible, CustomDebugStringConvertible{
|
|||
|
|
/// 有气泡时,content 最大宽度
|
|||
|
|
static let maxBubbleContentWidth = floor(UIScreen.width * 0.65)
|
|||
|
|
/// 无气泡时,content 最大宽度
|
|||
|
|
static let maxContentWidth = UIScreen.width - 32
|
|||
|
|
|
|||
|
|
var cellType: SessionCellType = .unknown
|
|||
|
|
|
|||
|
|
/// Seconds timestam;
|
|||
|
|
var timeStamp: TimeInterval = 0
|
|||
|
|
|
|||
|
|
var v2msg: V2NIMMessage?
|
|||
|
|
|
|||
|
|
/// 根据message创建的数据缓存
|
|||
|
|
var baseRemoteInfo: IMBaseRemoteInfo?
|
|||
|
|
|
|||
|
|
/// 是否是派生出来的model, 一般是一条消息显示两个cell中的卡片cell,本地追加创建的那种 默认NO
|
|||
|
|
var isDeriveModel = false
|
|||
|
|
/// 是否显示为卡片view
|
|||
|
|
var shouldShowCardView = false
|
|||
|
|
/// 是否显示为tips消息
|
|||
|
|
var shouldShowTips = false
|
|||
|
|
/// 是否靠左显示,根据消息发送方来确定
|
|||
|
|
var shouldShowLeft = false
|
|||
|
|
/// 默认YES,会显示气泡
|
|||
|
|
var shouldShowBubble = true
|
|||
|
|
|
|||
|
|
/// 缓存高度
|
|||
|
|
var cacheCellHeight: CGFloat = 0
|
|||
|
|
/// content配置类
|
|||
|
|
var config: IMContentBaseConfig!
|
|||
|
|
|
|||
|
|
var speechModel : SpeechModel?
|
|||
|
|
|
|||
|
|
var autoPlayAudioOnce: Bool = false
|
|||
|
|
var autoPlayAlreadyPlayed : Bool = false // 已经播放过了
|
|||
|
|
|
|||
|
|
/// 清空高度缓存
|
|||
|
|
public func clearCacheData() {
|
|||
|
|
cacheCellHeight = 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public func cellHeight() -> CGFloat {
|
|||
|
|
if cacheCellHeight > 0 {
|
|||
|
|
return cacheCellHeight
|
|||
|
|
}
|
|||
|
|
// 获取content的间距
|
|||
|
|
let size = config.contentSize(model: self)
|
|||
|
|
let cellInsets = config.cellInsets(model: self)
|
|||
|
|
let contentInsets = config.contentInsets(model: self)
|
|||
|
|
// 计算高度
|
|||
|
|
let height: CGFloat = size.height + cellInsets.top + cellInsets.bottom + contentInsets.top + contentInsets.bottom
|
|||
|
|
cacheCellHeight = height
|
|||
|
|
return height
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: Audio About
|
|||
|
|
|
|||
|
|
func prepareAudio(info: IMAIUserInfo?, completion:((_ model: SpeechModel?)-> Void)?){
|
|||
|
|
guard cellType == .aimsg, let text = v2msg?.text, text.count > 0, let voiceType = info?.voiceType else{
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
guard AudioPlayTool.audioChannelFreeToUse() else{
|
|||
|
|
dlog("Audio Channel 不可用")
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if let path = self.speechModel?.path, path.count > 0 {
|
|||
|
|
completion?(self.speechModel)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let saywords = String.removeBracketContents(from: text)
|
|||
|
|
guard saywords.count > 0 else{
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let speechRate = info?.dialogueSpeechRate ?? "0"
|
|||
|
|
let loudnessRate = info?.dialoguePitch ?? "0"
|
|||
|
|
|
|||
|
|
var params = [String: Any]()
|
|||
|
|
params.updateValue(saywords, forKey: "text")
|
|||
|
|
params.updateValue(voiceType, forKey: "voiceType")
|
|||
|
|
params.updateValue(speechRate, forKey: "speechRate")
|
|||
|
|
params.updateValue(loudnessRate, forKey: "pitchRate")
|
|||
|
|
if let aiId = IMAIViewModel.shared.aiIMInfo?.aiId{
|
|||
|
|
params.updateValue(aiId, forKey: "aiId")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
AICowProvider.request(.voiceTts(params: params), modelType: String.self) {[weak self] result in
|
|||
|
|
switch result {
|
|||
|
|
case .success(let model):
|
|||
|
|
if let audioString = model{
|
|||
|
|
self?.speechModel = SpeechModel.modelWithBase64String(audioString)
|
|||
|
|
//self?.speechModel?.refreshPath(path: audioString)
|
|||
|
|
completion?(self?.speechModel)
|
|||
|
|
}
|
|||
|
|
case .failure:
|
|||
|
|
completion?(nil)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// MARK: - Static
|
|||
|
|
// MARK: Equatable
|
|||
|
|
static func == (lhs: SessionBaseModel, rhs: SessionBaseModel) -> Bool {
|
|||
|
|
return lhs === rhs
|
|||
|
|
}
|
|||
|
|
/// 获取不同配置类
|
|||
|
|
public static func getContentConfigClass(type: SessionCellType) -> IMContentBaseConfig.Type {
|
|||
|
|
switch type {
|
|||
|
|
case .unknown:
|
|||
|
|
return IMContentBaseConfig.self
|
|||
|
|
case .text:
|
|||
|
|
return IMTextContentConfig.self
|
|||
|
|
case .aimsg:
|
|||
|
|
return IMAIMsgContentConfig.self
|
|||
|
|
case .timeStamp:
|
|||
|
|
return IMTimeStampContentConfig.self
|
|||
|
|
case .image:
|
|||
|
|
return IMImageContentConfig.self
|
|||
|
|
case .tips, .create, .keyword:
|
|||
|
|
return IMTipsContentConfig.self
|
|||
|
|
case .gift:
|
|||
|
|
return IMGiftContentConfig.self
|
|||
|
|
case .phonecall:
|
|||
|
|
return IMPhoneCallConfig.self
|
|||
|
|
case .aiLoading:
|
|||
|
|
return IMAIMsgLoadingConfig.self
|
|||
|
|
// default:
|
|||
|
|
// return IMContentBaseConfig.self
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Other Debug
|
|||
|
|
var description: String {
|
|||
|
|
var attchmentString = ""
|
|||
|
|
if let attchmentRaw = v2msg?.attachment?.raw{
|
|||
|
|
attchmentString = attchmentRaw
|
|||
|
|
}
|
|||
|
|
var icon = (v2msg?.isSelf ?? false) ? "✉️" :"💌"
|
|||
|
|
|
|||
|
|
return "\(icon)[\(cellType.rawValue)] 『\(v2msg?.text ?? "-")』raw:\(attchmentString)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var debugDescription: String {
|
|||
|
|
return "[\(cellType.rawValue)] 『\(v2msg?.text ?? "-")』"
|
|||
|
|
}
|
|||
|
|
}
|