Visual_Novel_iOS/crush/Crush/Src/Components/UI/Sheet/ChatModePickSheet.swift

402 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

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.

//
// ChatModePickSheet.swift
// Crush
//
// Created by Leon on 2025/8/17.
//
import UIKit
///
typealias ChatModelSelectionCallback = (AIChatModel) -> Void
///
class ChatModePickSheet: EGPopBaseView {
var closeButton: EPIconTertiaryButton!
var titleLabel: UILabel!
//
var scrollView: UIScrollView!
//
var modelCardViews: [ChatModelCardView] = []
var stayTunedLabel: CLLabel!
//
private var chatModels: [AIChatModel] = []
private var currentSelectedModelCode: String?
var cardHeight = 200.0//168.0
//
var selectionCallback: ChatModelSelectionCallback?
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(currentSelectedModelCode: String? = nil) {
self.currentSelectedModelCode = currentSelectedModelCode
super.init(direction: .bottom)
contentView.backgroundColor = .c.csbn
//
contentLength = 200 + UIWindow.safeAreaBottom
setupViews()
loadChatModels()
}
private func setupViews() {
closeButton = {
let v = EPIconTertiaryButton(radius: .round, iconSize: .small, iconCode: .delete)
v.addTarget(self, action: #selector(bgButtonPressed), for: .touchUpInside)
contentView.addSubview(v)
v.snp.makeConstraints { make in
make.top.equalToSuperview().offset(20)
make.size.equalTo(v.bgImageSize())
make.trailing.equalToSuperview().offset(-16)
}
return v
}()
titleLabel = {
let v = UILabel()
v.font = .t.ttm
v.textColor = .text
v.textAlignment = .center
contentView.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(24)
make.trailing.equalToSuperview().offset(-24)
make.top.equalToSuperview().offset(32)
}
return v
}()
scrollView = {
let v = UIScrollView()
v.showsVerticalScrollIndicator = false
v.showsHorizontalScrollIndicator = false
v.delegate = self
contentView.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.top.equalTo(titleLabel.snp.bottom).offset(24)
make.height.equalTo(cardHeight) //
}
return v
}()
stayTunedLabel = {
let v = CLLabel()
v.font = .t.tbs
v.textColor = .c.ctsn
contentView.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(24)
make.top.equalTo(scrollView.snp.bottom).offset(16)
}
return v
}()
titleLabel.text = "Dialog Model"
stayTunedLabel.text = "Stay tuned for more models"
}
private func loadChatModels() {
guard let models = AppDictManager.shared.chatModels, models.count > 0 else {
//
AppDictManager.shared.loadChatModelDict { [weak self] success in
if success {
self?.loadChatModels()
}
}
return
}
chatModels = models
setupModelCards()
}
private func setupModelCards() {
//
modelCardViews.forEach { $0.removeFromSuperview() }
modelCardViews.removeAll()
//
for (index, model) in chatModels.enumerated() {
let cardView = ChatModelCardView()
cardView.configure(with: model, isSelected: model.code == currentSelectedModelCode)
cardView.tag = index
cardView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(modelCardTapped(_:))))
scrollView.addSubview(cardView)
modelCardViews.append(cardView)
cardView.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.width.equalTo(scrollView.snp.width)
make.height.equalTo(cardHeight) //
if index == 0 {
make.top.equalToSuperview()
} else {
make.top.equalTo(modelCardViews[index - 1].snp.bottom).offset(12)
}
if index == chatModels.count - 1 {
make.bottom.equalToSuperview()
}
}
}
// contentLength
updateContentLength()
//
// DispatchQueue.main.async { [weak self] in
// self?.scrollToSelectedModel()
// }
}
private func updateContentLength() {
// + +
let baseHeight: CGFloat = 32 + 24 + 16 + 20 + UIWindow.safeAreaBottom// titleLabel top + spacing + stayTunedLabel + bottom margin
// 200 + 12
let cardHeight: CGFloat = cardHeight
let cardSpacing: CGFloat = 12
let totalCardHeight = CGFloat(chatModels.count) * cardHeight + CGFloat(max(0, chatModels.count - 1)) * cardSpacing
// 2
let maxVisibleHeight = min(totalCardHeight, cardHeight * 2 + cardSpacing)
// scrollView
scrollView.snp.updateConstraints { make in
make.height.equalTo(maxVisibleHeight)
}
// contentLength
contentLength = baseHeight + maxVisibleHeight + UIWindow.safeAreaBottom
//
layoutIfNeeded()
}
private func scrollToSelectedModel() {
guard let selectedCode = currentSelectedModelCode,
let selectedIndex = chatModels.firstIndex(where: { $0.code == selectedCode }),
selectedIndex < chatModels.count else { return }
// scrollViewframe
guard scrollView.frame.height > 0 else {
// frame
DispatchQueue.main.async { [weak self] in
self?.scrollToSelectedModel()
}
return
}
let offsetY = CGFloat(selectedIndex) * (cardHeight + 12) // +
scrollView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: false)
}
@objc private func modelCardTapped(_ gesture: UITapGestureRecognizer) {
guard let cardView = gesture.view as? ChatModelCardView,
let index = modelCardViews.firstIndex(of: cardView),
index < chatModels.count else { return }
let selectedModel = chatModels[index]
//
modelCardViews.forEach { $0.setSelected(false) }
cardView.setSelected(true)
//
selectionCallback?(selectedModel)
//
dismiss()
}
}
// MARK: - UIScrollViewDelegate
extension ChatModePickSheet: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//
}
}
// MARK: - ChatModelCardView
class ChatModelCardView: UIView {
private var block: UIView!
private var modelName: UILabel!
private var modelDesc: UILabel!
private var selectMark: UIImageView!
private var innerBlock: UIView!
private var queryButton: EPIconTertiaryButton!
private var priceStackView: UIStackView!
private var model: AIChatModel?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
block = {
let v = UIView()
v.backgroundColor = .c.csen
v.cornerRadius = 16
addSubview(v)
v.snp.makeConstraints { make in
// make.edges.equalToSuperview()
make.leading.equalToSuperview().offset(24)
make.trailing.equalToSuperview().offset(-24)
make.top.bottom.equalToSuperview()
}
return v
}()
selectMark = {
let v = UIImageView()
v.image = UIImage(named: "checkmark_tick")
v.isHidden = true
block.addSubview(v)
v.snp.makeConstraints { make in
make.size.equalTo(CGSize(width: 20, height: 20))
make.top.equalToSuperview().offset(18)
make.trailing.equalToSuperview().offset(-16)
}
return v
}()
modelName = {
let v = CLLabel()
v.font = .t.tts
v.setContentCompressionResistancePriority(UILayoutPriority(749), for: .horizontal)
block.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.top.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(selectMark.snp.leading).offset(-8)
}
return v
}()
queryButton = {
let v = EPIconTertiaryButton(radius: .round, iconSize: .xs10, iconCode: .question)
v.addTarget(self, action: #selector(tapQueryButton), for: .touchUpInside)
block.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalTo(modelName.snp.trailing).offset(4)
make.centerY.equalTo(modelName)
make.trailing.lessThanOrEqualToSuperview().offset(-16)
}
return v
}()
modelDesc = {
let v = CLLabel()
v.font = .t.tbs
v.textColor = .c.ctsn
block.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
make.top.equalTo(modelName.snp.bottom).offset(4)
}
return v
}()
innerBlock = {
let v = UIView()
v.backgroundColor = .c.csdn
v.cornerRadius = 12
block.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.equalToSuperview().offset(-16)
//make.top.equalTo(modelDesc.snp.bottom).offset(12)
make.top.equalToSuperview().offset(76)
make.height.equalTo(108) // 76
//make.bottom.equalToSuperview().offset(-16)
}
return v
}()
priceStackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 12
v.alignment = .leading
innerBlock.addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(12)
make.trailing.equalToSuperview().offset(-12)
make.top.equalToSuperview().offset(12)
make.bottom.equalToSuperview().offset(-12)
}
return v
}()
}
func configure(with model: AIChatModel, isSelected: Bool = false) {
self.model = model
modelName.text = model.name ?? ""
modelDesc.text = model.description ?? ""
//
priceStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
//
if let price = model.textPrice {
let coin = Coin(cents: price)
let priceLabel = CLIconLabel()
priceLabel.iconImageView.image = UIImage(named: "icon_16_diamond")
priceLabel.contentLabel.text = "\(coin.thousandthsFormatted)/Text Message"
priceStackView.addArrangedSubview(priceLabel)
}
if let price = model.voicePrice {
let coin = Coin(cents: price)
let priceLabel = CLIconLabel()
priceLabel.iconImageView.image = UIImage(named: "icon_16_diamond")
priceLabel.contentLabel.text = "\(coin.thousandthsFormatted)/Send or play voice"
priceStackView.addArrangedSubview(priceLabel)
}
if let price = model.voiceChatPrice {
let coin = Coin(cents: price)
let priceLabel = CLIconLabel()
priceLabel.iconImageView.image = UIImage(named: "icon_16_diamond")
priceLabel.contentLabel.text = "\(coin.thousandthsFormatted)/ 1 min Voice call"
priceStackView.addArrangedSubview(priceLabel)
}
setSelected(isSelected)
}
func setSelected(_ selected: Bool) {
selectMark.isHidden = !selected
// block.layer.borderWidth = selected ? 2 : 0
// block.layer.borderColor = selected ? UIColor.c.cpn.cgColor : UIColor.clear.cgColor
}
@objc func tapQueryButton(){
let content = "*文本消息价格是指与角色进行文本消息对话的价格,含发送文本,发送图片,发送礼物;按条计算\n\n*发送语音消息价格是指与角色发送语音或者播放角色的语音的价格;按次计算\n\n*语音通话消息价格是指与角色进行语音电话对话的价格;按分钟计算"
let alert = Alert(title: "Tips", text: content)
let action1 = AlertAction(title: "Got it", actionStyle: .confirm) {
}
alert.addAction(action1)
alert.show()
}
}