// // CoinsRechargeSheet.swift // Crush // // Created by Leon on 2025/9/16. // import UIKit import Combine class CoinsRechargeSheet: EGPopBaseView { var titleLabel: UILabel! var descLabel: CLLabel! var closeButton: EPIconTertiaryButton! var bottomView: UIView! var balanceLabel: CLIconLabel! var rechargeButton: StyleButton! var layout = UICollectionViewFlowLayout() var cv: UICollectionView! // MARK: - 数据源和状态管理 private var cancellables = Set() @Published private var datas = [IAPProducts]() @Published private var selectIndex = 0 @Published private var selectProduct: IAPProducts? private let viewModel = CoinsRechargeViewModel() init() { super.init(direction: .bottom) contentView.backgroundColor = .c.csbn contentLength = 549 + UIWindow.safeAreaBottom setupViews() setupDatas() setupEvents() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupViews() { contentView.backgroundColor = .c.csbn 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 }() descLabel = { let v = CLLabel() v.font = .t.tbs v.numberOfLines = 2 contentView.addSubview(v) v.snp.makeConstraints { make in make.leading.equalToSuperview().offset(CGFloat.lrs) make.trailing.equalToSuperview().offset(-CGFloat.lrs) // make.top.equalTo(titleLabel.snp.bottom).offset(24) // make.top.equalToSuperview().offset(80) make.centerY.equalTo(contentView.snp.top).offset(80 + 10) } return v }() bottomView = { let v = UIView() contentView.addSubview(v) v.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.bottom.equalToSuperview().offset(-UIWindow.safeAreaBottom) make.height.equalTo(80) } return v }() balanceLabel = { let v = CLIconLabel() v.spacing = 8 v.iconSize = CGSize(width: 24, height: 24) v.iconImageView.image = UIImage.icon32Diamond v.contentLabel.textColor = .text v.contentLabel.font = .t.tndm v.contentLabel.text = "0" bottomView.addSubview(v) v.snp.makeConstraints { make in make.leading.equalToSuperview().offset(24) make.centerY.equalToSuperview() } return v }() rechargeButton = { let v = StyleButton() v.primary(size: .large) v.addTarget(self, action: #selector(rechargeButtonAction), for: .touchUpInside) bottomView.addSubview(v) v.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-24) //make.leading.equalToSuperview().offset(<#T##amount: any ConstraintOffsetTarget##any ConstraintOffsetTarget#>) make.leading.greaterThanOrEqualTo(balanceLabel.snp.trailing).offset(16) } return v }() do { let width = floor((UIScreen.width - 24 * 2 - 16) * 0.5) let height = 96.0 layout.scrollDirection = .vertical layout.itemSize = CGSize(width: width, height: height) layout.minimumLineSpacing = 16 layout.minimumInteritemSpacing = 16 layout.sectionInset = UIEdgeInsets(top: 0, left: 24, bottom: 0, right: 24) cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .clear cv.showsHorizontalScrollIndicator = false cv.delegate = self cv.dataSource = self cv.contentInsetAdjustmentBehavior = .never cv.register(CoinsRechargeGridCell.self, forCellWithReuseIdentifier: "CoinsRechargeGridCell") contentView.addSubview(cv) cv.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.top.equalToSuperview().offset(136) //make.bottom.equalToSuperview() make.bottom.equalTo(bottomView.snp.top) } } titleLabel.text = "Crush Coin insufficient" descLabel.text = "The Crush coin is insufficient and cannot continue, please recharge" rechargeButton.setTitle("Recharge", for: .normal) } // MARK: - 事件设置 private func setupEvents() { // 监听选择索引变化 $selectIndex.sink { [weak self] index in guard let self = self else { return } if index < self.datas.count { self.selectProduct = self.datas[index] } }.store(in: &cancellables) $datas.sink {[weak self] products in self?.selectIndex = self?.selectIndex ?? 0 }.store(in: &cancellables) // 监听钱包余额变化 WalletCore.shared.$balance.sink { [weak self] wallet in self?.balanceLabel.contentLabel.text = wallet.displayBalance() // DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { // self?.dismiss() // } }.store(in: &cancellables) NotificationCenter.default.addObserver(self, selector: #selector(notifyChargeDone), name: AppNotificationName.chargeDonePushTradeId.notificationName, object: nil) } @objc private func notifyChargeDone(){ dismiss() } // MARK: - 数据加载 private func setupDatas() { loadBalance() loadCoinsTiers() } private func loadBalance() { WalletCore.shared.refreshWallet() } private func loadCoinsTiers() { CLPurchase.shared.loadCoinTiersProducts { [weak self] response in self?.handleValidProductsInEpalServer(products: response?.productList) } } // MARK: - 处理有效产品 private func handleValidProductsInEpalServer(products: [IAPProducts]?) { guard let items = products else { return } datas = items cv.reloadData() // 显示加载指示器 Hud.showIndicator() IAPCore.shared.requestProducts(items) { [weak self] iapIds in Hud.hideIndicator() var validProducts: [IAPProducts] = [] for id in iapIds { for per in items { if id == per.productId { validProducts.append(per) continue } } } self?.datas = validProducts self?.cv.reloadData() } } // MARK: - 充值按钮点击 @objc private func rechargeButtonAction() { guard let selectedProduct = selectProduct else { Hud.toast(str: "Please select a recharge option") return } // 开始购买流程 viewModel.purchase(product: selectedProduct) { [weak self] success, tradeNo in if success, let tradeId = tradeNo { selectedProduct.tradeId = tradeId IAPCore.shared.addPayProductId(productId: selectedProduct.productId ?? "", tradeId: tradeId) } else { Hud.toast(str: "Purchase failed, please try again") } } } } extension CoinsRechargeSheet: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return datas.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CoinsRechargeGridCell", for: indexPath) as! CoinsRechargeGridCell let data = datas[indexPath.item] cell.block.backgroundColor = .c.csnn cell.config(data) cell.setupSelected(selected: indexPath.item == selectIndex) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { selectIndex = indexPath.item collectionView.reloadData() } }