304 lines
10 KiB
Swift
304 lines
10 KiB
Swift
|
|
//
|
|||
|
|
// ChatModePickInsufficientCoinSheet.swift
|
|||
|
|
// Crush
|
|||
|
|
//
|
|||
|
|
// Created by Leon on 2025/9/23.
|
|||
|
|
//
|
|||
|
|
import UIKit
|
|||
|
|
import Combine
|
|||
|
|
|
|||
|
|
/// 余额不足,对话模型选择
|
|||
|
|
class ChatModePickInsufficientCoinSheet: EGPopBaseView {
|
|||
|
|
var closeButton: EPIconTertiaryButton!
|
|||
|
|
var titleLabel: UILabel!
|
|||
|
|
var subTitleLabel: LineSpaceLabel!
|
|||
|
|
|
|||
|
|
// 滚动视图
|
|||
|
|
var scrollView: UIScrollView!
|
|||
|
|
|
|||
|
|
// 模型卡片视图数组
|
|||
|
|
var modelCardViews: [ChatModelCardView] = []
|
|||
|
|
|
|||
|
|
var bottomView: UIView!
|
|||
|
|
var balance: CLIconLabel!
|
|||
|
|
var operateButton: StyleButton! // Recharge
|
|||
|
|
|
|||
|
|
// 数据源
|
|||
|
|
private var chatModels: [AIChatModel] = []
|
|||
|
|
private var currentSelectedModelCode: String?
|
|||
|
|
|
|||
|
|
var cardHeight = 168.0
|
|||
|
|
// 选择回调
|
|||
|
|
var selectionCallback: ChatModelSelectionCallback?
|
|||
|
|
|
|||
|
|
private var cancellables = Set<AnyCancellable>()
|
|||
|
|
|
|||
|
|
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()
|
|||
|
|
setupDatas()
|
|||
|
|
setupEvent()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
subTitleLabel = {
|
|||
|
|
let v = LineSpaceLabel()
|
|||
|
|
let typo = CLSystemToken.typography(token: .tbs)
|
|||
|
|
v.config(typo)
|
|||
|
|
v.textColor = .c.ctpn
|
|||
|
|
contentView.addSubview(v)
|
|||
|
|
v.snp.makeConstraints { make in
|
|||
|
|
make.top.equalTo(titleLabel.snp.bottom).offset(24)
|
|||
|
|
make.leading.equalToSuperview().offset(CGFloat.lrs)
|
|||
|
|
make.trailing.equalToSuperview().offset(-CGFloat.lrs)
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
scrollView = {
|
|||
|
|
let v = UIScrollView()
|
|||
|
|
v.showsVerticalScrollIndicator = false
|
|||
|
|
v.showsHorizontalScrollIndicator = false
|
|||
|
|
contentView.addSubview(v)
|
|||
|
|
v.snp.makeConstraints { make in
|
|||
|
|
make.leading.equalToSuperview()
|
|||
|
|
make.trailing.equalToSuperview()
|
|||
|
|
make.top.equalTo(subTitleLabel.snp.bottom).offset(16) // 24
|
|||
|
|
make.height.equalTo(cardHeight) // 每个卡片的高度
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
bottomView = {
|
|||
|
|
let v = UIView()
|
|||
|
|
contentView.addSubview(v)
|
|||
|
|
v.snp.makeConstraints { make in
|
|||
|
|
make.bottom.equalToSuperview().offset(-UIWindow.safeAreaBottom)
|
|||
|
|
make.leading.trailing.equalToSuperview()
|
|||
|
|
make.height.equalTo(80)
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
balance = {
|
|||
|
|
let v = CLIconLabel()
|
|||
|
|
v.iconSize = CGSize(width: 16, height: 16)
|
|||
|
|
v.iconImageView.image = .icon16Diamond
|
|||
|
|
v.contentLabel.textColor = .white
|
|||
|
|
v.contentLabel.font = .t.tlm
|
|||
|
|
bottomView.addSubview(v)
|
|||
|
|
v.snp.makeConstraints { make in
|
|||
|
|
make.centerY.equalToSuperview()
|
|||
|
|
make.leading.equalToSuperview().offset(24)
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
operateButton = {
|
|||
|
|
let v = StyleButton()
|
|||
|
|
v.primary(size: .large)
|
|||
|
|
v.addTarget(self, action: #selector(operateButtonTapped), for: .touchUpInside)
|
|||
|
|
bottomView.addSubview(v)
|
|||
|
|
v.snp.makeConstraints { make in
|
|||
|
|
make.centerY.equalToSuperview()
|
|||
|
|
make.trailing.equalToSuperview().offset(-24)
|
|||
|
|
make.leading.greaterThanOrEqualTo(balance.snp.trailing).offset(48)
|
|||
|
|
}
|
|||
|
|
return v
|
|||
|
|
}()
|
|||
|
|
|
|||
|
|
titleLabel.text = "Dialog Model"
|
|||
|
|
subTitleLabel.text = "The Crush coin is insufficient and cannot continue to send messages. The charging standard of the current model:"
|
|||
|
|
operateButton.setTitle("Recharge", for: .normal)
|
|||
|
|
|
|||
|
|
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func setupDatas(){
|
|||
|
|
// let group = DispatchGroup()
|
|||
|
|
//
|
|||
|
|
// group.enter()
|
|||
|
|
loadChatModels {
|
|||
|
|
// group.leave()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// group.enter()
|
|||
|
|
// loadChatSetting {
|
|||
|
|
// group.leave()
|
|||
|
|
// }
|
|||
|
|
//
|
|||
|
|
// group.notify(queue: .main) {[weak self] in
|
|||
|
|
// self?.setupModelCards()
|
|||
|
|
// }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func setupEvent(){
|
|||
|
|
WalletCore.shared.$balance.sink {[weak self] balance in
|
|||
|
|
if let priceLabel = self?.balance {
|
|||
|
|
priceLabel.contentLabel.text = balance.displayBalance()
|
|||
|
|
}
|
|||
|
|
}.store(in: &cancellables)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private func loadChatModels(completion:(()->Void)? = nil) {
|
|||
|
|
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()
|
|||
|
|
completion?()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
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 subTitleHeight = subTitleLabel.sizeThatFits(CGSize(width: UIScreen.width - CGFloat.lrs*2, height: CGFLOAT_MAX)).height
|
|||
|
|
//dlog("文字高度: \(subTitleHeight)")
|
|||
|
|
// 基础高度:标题 + 间距 + 底部标签
|
|||
|
|
let baseHeight: CGFloat = 80 + subTitleHeight + 80 + 16// 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()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Function
|
|||
|
|
private func scrollToSelectedModel() {
|
|||
|
|
guard let selectedCode = currentSelectedModelCode,
|
|||
|
|
let selectedIndex = chatModels.firstIndex(where: { $0.code == selectedCode }),
|
|||
|
|
selectedIndex < chatModels.count else { return }
|
|||
|
|
|
|||
|
|
// 确保scrollView已经有正确的frame
|
|||
|
|
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)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// MARK: - Action
|
|||
|
|
@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)
|
|||
|
|
|
|||
|
|
IMAIViewModel.shared.updateChatModel(code: selectedModel.code, completion: nil)
|
|||
|
|
|
|||
|
|
// 关闭弹窗
|
|||
|
|
dismiss()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@objc private func operateButtonTapped(){
|
|||
|
|
dismiss()
|
|||
|
|
AppRouter.goWalletCenter()
|
|||
|
|
}
|
|||
|
|
}
|