// // LNExchangePanel.swift // Lanu // // Created by OneeChan on 2026/1/8. // import Foundation import UIKit import SnapKit enum LNExchangeType { case coinToDiamond case diamondToCoin case beanToCoin case beanToDiamond var exchangeType: (from: LNCurrencyType, to: LNCurrencyType) { switch self { case .coinToDiamond: (.coin, .diamond) case .diamondToCoin: (.diamond, .coin) case .beanToCoin: (.bean, .coin) case .beanToDiamond: (.bean, .diamond) } } } class LNExchangePanel: LNPopupView { private let exchangeType: LNExchangeType private let exchangeView = UIView() private let remainLabel = UILabel() private let exchangeInput = UITextField() private let expectLabel = UILabel() private let exchangeButton = UIButton() private var delayTaskId: String? init(exchangeType: LNExchangeType) { self.exchangeType = exchangeType super.init(frame: .zero) setupViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNExchangePanel: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let currentText = textField.text ?? "" guard let range = Range(range, in: currentText) else { return false } let newText = currentText.replacingCharacters(in: range, with: string) if newText.isEmpty { return true } if newText.starts(with: "00") { return false } if newText.filter({ $0 == "." }).count > 2 { return false } guard Double(newText) != nil else { return false } return true } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } } extension LNExchangePanel { private func checkExchangeButton() { let text = exchangeInput.text ?? "" if text.isEmpty { exchangeInput.attributedPlaceholder = .init(string: .init(key: "A00261"), attributes: [.font: UIFont.heading_h3, .foregroundColor: UIColor.text_2]) } else { exchangeInput.attributedPlaceholder = nil } fetchExchangeResult() } private func fetchExchangeResult() { LNDelayTask.cancel(key: delayTaskId) exchangeButton.isEnabled = false exchangeButton.setBackgroundImage(nil, for: .normal) delayTaskId = LNDelayTask.perform(delay: 1.0) { [weak self] in guard let self else { return } let text = exchangeInput.text ?? "" let value = Double(text) ?? 0 if value == 0 { expectLabel.text = "0" return } LNPurchaseManager.shared.expectResult(exchangeType: exchangeType, count: value) { [weak self] result in guard let self else { return } guard exchangeInput.text == text else { return } if let result { expectLabel.text = "\(result)" exchangeButton.isEnabled = true exchangeButton.setBackgroundImage(.primary_8, for: .normal) } else { expectLabel.text = "" } } } } private func setupViews() { let titleView = buildTitleView() container.addSubview(titleView) titleView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview() } let remainView = buildRemain() container.addSubview(remainView) remainView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(titleView.snp.bottom).offset(4) } exchangeView.backgroundColor = .fill_1 exchangeView.layer.cornerRadius = 12 container.addSubview(exchangeView) exchangeView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(remainView.snp.bottom).offset(4) } let diamondExchange = buildExchangeView() exchangeView.addSubview(diamondExchange) diamondExchange.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview() } let line = UIView() line.backgroundColor = .fill_3 exchangeView.addSubview(line) line.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(10) make.top.equalTo(diamondExchange.snp.bottom).offset(4) make.height.equalTo(1) } let coinView = buildExpectView() exchangeView.addSubview(coinView) coinView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.bottom.equalToSuperview() make.top.equalTo(line.snp.bottom).offset(4) } exchangeButton.setTitle(.init(key: "A00262"), for: .normal) exchangeButton.setTitleColor(.text_1, for: .normal) exchangeButton.titleLabel?.font = .heading_h3 exchangeButton.backgroundColor = .text_2 exchangeButton.layer.cornerRadius = 23.5 exchangeButton.clipsToBounds = true exchangeButton.isEnabled = false exchangeButton.setBackgroundImage(nil, for: .normal) exchangeButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } guard let amout = Double(exchangeInput.text ?? "") else { return } guard amout > 0 else { return } LNPurchaseManager.shared.exchangeCurrency(exchangeType: exchangeType, amount: amout) { [weak self] success in guard let self else { return } guard success else { return } dismiss() showToast(.init(key: "A00263")) } }), for: .touchUpInside) container.addSubview(exchangeButton) exchangeButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.height.equalTo(47) make.top.equalTo(exchangeView.snp.bottom).offset(24) make.bottom.equalToSuperview().offset(commonBottomInset) } } private func buildTitleView() -> UIView { let titleView = UIView() titleView.isUserInteractionEnabled = false titleView.snp.makeConstraints { make in make.height.equalTo(50) } let titleLabel = UILabel() titleLabel.text = switch exchangeType { case .coinToDiamond, .beanToDiamond: .init(key: "A00264") case .diamondToCoin, .beanToCoin: .init(key: "A00273") } titleLabel.font = .heading_h3 titleLabel.textColor = .text_5 titleView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() } return titleView } private func buildRemain() -> UIView { let container = UIView() container.onTap { [weak self] in guard let self else { return } endEditing(true) } container.snp.makeConstraints { make in make.height.equalTo(36) } let exchange = UIButton() exchange.setTitle(.init(key: "A00265"), for: .normal) exchange.setTitleColor(.text_6, for: .normal) exchange.titleLabel?.font = .body_xs exchange.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } exchangeInput.text = switch exchangeType { case .coinToDiamond: myWalletInfo.coin.toDisplay case .diamondToCoin: myWalletInfo.diamond.toDisplay case .beanToCoin, .beanToDiamond: myWalletInfo.availableBean.toDisplay } checkExchangeButton() }), for: .touchUpInside) container.addSubview(exchange) exchange.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } let currencyIcon: UIImageView = switch exchangeType { case .coinToDiamond: .coinImageView() case .diamondToCoin: .diamondImageView() case .beanToCoin, .beanToDiamond: .beanImageView() } container.addSubview(currencyIcon) currencyIcon.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() make.width.height.equalTo(24) } remainLabel.text = switch exchangeType { case .coinToDiamond: myWalletInfo.coin.toDisplay case .diamondToCoin: myWalletInfo.diamond.toDisplay case .beanToCoin, .beanToDiamond: myWalletInfo.availableBean.toDisplay } remainLabel.font = .heading_h1 remainLabel.textColor = .text_5 container.addSubview(remainLabel) remainLabel.snp.makeConstraints { make in make.leading.equalTo(currencyIcon.snp.trailing).offset(2) make.centerY.equalToSuperview() } return container } private func buildExchangeView() -> UIView { let container = UIView() container.onTap { [weak self] in guard let self else { return } endEditing(true) } container.snp.makeConstraints { make in make.height.equalTo(67) } let titleLabel = UILabel() titleLabel.text = switch exchangeType { case .coinToDiamond: .init(key: "A00266") case .diamondToCoin: .init(key: "A00274") case .beanToCoin, .beanToDiamond: .init(key: "B00106") } titleLabel.font = .heading_h4 titleLabel.textColor = .text_5 container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(12) make.centerY.equalToSuperview() } exchangeInput.font = .heading_h2 exchangeInput.textColor = .text_5 exchangeInput.visibleView = exchangeView exchangeInput.delegate = self exchangeInput.returnKeyType = .done exchangeInput.attributedPlaceholder = .init( string: .init(key: "A00261"), attributes: [.font: UIFont.heading_h3, .foregroundColor: UIColor.text_2]) exchangeInput.keyboardType = .decimalPad exchangeInput.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } checkExchangeButton() }), for: .editingChanged) container.addSubview(exchangeInput) exchangeInput.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-12) make.centerY.equalToSuperview() } let expectIcon: UIImageView = switch exchangeType { case .coinToDiamond: .coinImageView() case .diamondToCoin: .diamondImageView() case .beanToCoin, .beanToDiamond: .beanImageView() } container.addSubview(expectIcon) expectIcon.snp.makeConstraints { make in make.trailing.equalTo(exchangeInput.snp.leading).offset(-2) make.centerY.equalToSuperview() make.width.height.equalTo(20) } return container } private func buildExpectView() -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(67) } let titleLabel = UILabel() titleLabel.text = switch exchangeType { case .coinToDiamond, .beanToDiamond: .init(key: "A00267") case .diamondToCoin, .beanToCoin: .init(key: "A00275") } titleLabel.font = .heading_h4 titleLabel.textColor = .text_5 container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(12) make.centerY.equalToSuperview() } expectLabel.text = "0" expectLabel.font = .heading_h2 expectLabel.textColor = .text_5 container.addSubview(expectLabel) expectLabel.snp.makeConstraints { make in make.trailing.equalToSuperview().offset(-12) make.centerY.equalToSuperview() } let expectIcon: UIImageView = switch exchangeType { case .coinToDiamond, .beanToDiamond: .diamondImageView() case .diamondToCoin, .beanToCoin: .coinImageView() } container.addSubview(expectIcon) expectIcon.snp.makeConstraints { make in make.trailing.equalTo(expectLabel.snp.leading).offset(-2) make.centerY.equalToSuperview() make.width.height.equalTo(20) } return container } }