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