Visual_Novel_iOS/crush/Crush/Src/Modules/Chat/ContentView/IMAIMsgContentView.swift

226 lines
7.8 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

//
// IMAIMsgContentView.swift
// Crush
//
// Created by Leon on 2025/8/20.
//
import UIKit
import ActiveLabel
class IMAIMsgContentConfig: IMContentBaseConfig {
override func contentSize(model: SessionBaseModel) -> CGSize {
guard model.v2msg != nil else { return .zero }
let contentView = IMAIMsgContentView.init(frame: .zero)
let content = contentView.contentWith(model: model)
// Way 1 to calculate
//contentView.contentLabel.attributedText = contentView.formatAttrubuteString(string: content)
//var size = contentView.contentLabel.sizeThatFits(CGSize(width: SessionBaseModel.maxBubbleContentWidth, height: CGFloat.greatestFiniteMagnitude))
// Way 2 to calculate
let attributedString = contentView.formatAttrubuteString(string: content)
var size = attributedString.boundingRect(
with: CGSize(width: SessionBaseModel.maxBubbleContentWidth, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil
).size
let calculatedHeight = ceil(size.height)
size.height = calculatedHeight
// dlog("\(SessionBaseModel.maxBubbleContentWidth) :\(size.height)")
if size.height < 20 {
size = CGSize(width: size.width, height: 20)
}
return size
}
override func cellInsets(model: SessionBaseModel) -> UIEdgeInsets {
return UIEdgeInsets(top: 0, left: 24, bottom: 8, right: 16)
}
override func contentInsets(model: SessionBaseModel) -> UIEdgeInsets {
return UIEdgeInsets(top: 36, left: 16, bottom: 16, right: 16)
}
override func contentViewClass(model: SessionBaseModel) -> IMContentBaseView.Type {
return IMAIMsgContentView.self
}
}
class IMAIMsgContentView: IMContentBaseView{
var effectView: UIVisualEffectView!
var contentLabel: LineSpaceLabel! // ActiveLabel
var audioView : IMAudioFlagView!
lazy var audioHelper = IMAudioHelper()
required override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI() {
effectView = {
let v = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
v.alpha = 1
v.backgroundColor = .c.csedn
v.cornerRadius = 16
insertSubview(v, at: 0)
v.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 16, left: 0, bottom: 0, right: 0))
}
return v
}()
contentLabel = {
//let v = ActiveLabel()
//v.font = CLSystemToken.font(token: .tbm)
let v = LineSpaceLabel()
let typo = CLSystemToken.typography(token: .tbm)
v.config(typo)
v.textColor = UIColor.c.ctsn
v.numberOfLines = 0
v.textColor = .white
containerView.addSubview(v)
v.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
return v
}()
audioView = {
let v = IMAudioFlagView()
v.topButton.addTarget(self, action: #selector(tapAudioButton), for: .touchUpInside)
addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.top.equalToSuperview().offset(4) // -12
}
return v
}()
contentLabel.text = ""
//contentLabel.textColor = .white
// Long press gesture
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPressGesture(_:)))
addGestureRecognizer(longPressGesture)
}
func contentWith(model: SessionBaseModel) -> String {
var content: String? = nil
let message = model.v2msg
if String.realEmpty(str: content) {
content = message?.text
}
// #warning("test")
// content = "(Watching her parents toast you respectfully, I feel very uncomfortable. After all, she has been standing on the top of the magic capital since she was a child. She has never seen her parents like this, but should I say that he is really handsome?) Are you?"
// dlog("content:\(String(describing: content)), createTime:\(String(describing: message?.createTime)), modifytime:\(String(describing: message?.modifyTime))")
return content ?? ""
}
func formatAttrubuteString(string: String) -> NSMutableAttributedString{
let content = string
let basic = [NSAttributedString.Key.font: UIFont.t.tbm,
NSAttributedString.Key.foregroundColor: UIColor.white,
]
let aStr = NSMutableAttributedString(string: content, attributes: basic)
//content.withAttributes([.font(.t.tbm), .textColor(.text)])
let ranges = String.findBracketRanges(in: content)
let att = [NSAttributedString.Key.foregroundColor: UIColor.c.ctsn]
for range in ranges {
aStr.addAttributes(att, range: range)
}
return aStr
}
override func refreshModel(model: SessionBaseModel) {
super.refreshModel(model: model)
let content = contentWith(model:model)
//
var speedrate = 0
if let userSpeed = IMAIViewModel.shared.aiIMInfo?.dialogueSpeechRate, let intSpeed = Int(userSpeed){
speedrate = intSpeed
}
let duration = audioHelper.calculateAudioDuration(text: content, speechRate: speedrate)
audioView.secondsLabel.text = duration.imAIaudioDurationString
contentLabel.attributedText = formatAttrubuteString(string: content)
audioView.reloadState(with: model.speechModel)
if model.autoPlayAudioOnce && model.autoPlayAlreadyPlayed == false{
tapAudioButton()
model.autoPlayAlreadyPlayed = true
}
}
// MARK: - Action
@objc private func handleLongPressGesture(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == .began {
// dlog("Long press detected")
let event = IMEventModel()
event.eventType = .aiMsgLongPress
event.cellModel = self.model
event.senderView = self
delegate?.onTapAction(event: event)
}
}
@objc private func tapAudioButton(){
// let text = contentWith(model: self.model)
// Generate voice(mp3 base64string)
dlog("tap Audio button...")
let event = IMEventModel()
event.eventType = .playAITextToAudio
event.cellModel = self.model
event.senderView = self
delegate?.onTapAction(event: event)
audioView.startLoading()
}
// MARK: - Helper
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
//
let view = super.hitTest(point, with: event)
if view != nil {
return view
}
// UIButton
for subview in subviews {
// UIButton
guard subview is UIButton else { continue }
//
let convertedPoint = subview.convert(point, from: self)
//
if subview.bounds.contains(convertedPoint) {
return subview.hitTest(convertedPoint, with: event)
}
}
return nil
}
}