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