// // LNCreateOrderPanel.swift // Lanu // // Created by OneeChan on 2025/12/22. // import Foundation import UIKit import SnapKit class LNCreateOrderPanel: LNPopupView { private let gameNameLabel = UILabel() private let avatar = UIImageView() private let nameLabel = UILabel() private let priceLabel = UILabel() private let countEditView = UIView() private let minuButton = UIButton() private let customCountLabel = UILabel() private let addButton = UIButton() private let countLabel = UILabel() private let costLabel = UILabel() private let discountView = UIView() private let newbieView = LNNewbieDiscountView() private let discountCoinLabel = UILabel() private let extraView = UIView() private let extraInput = UITextField() private let curCoinLabel = UILabel() private var skillId: String? private var qrCode: String? private var price: Double = 0.0 private var curCount = 1 { didSet { countLabel.text = "x\(curCount)" customCountLabel.text = "\(curCount)" if let discount = LNOrderManager.shared.discountFor(price) { costLabel.text = (price * Double(curCount) - (1 - discount) * price).toDisplay } else { costLabel.text = (price * Double(curCount)).toDisplay } if curCount <= 1 { minuButton.isEnabled = false } else { minuButton.isEnabled = true } } } var editable: Bool = false { didSet { extraView.isHidden = !editable countEditView.isHidden = !editable countLabel.isHidden = editable } } var scene: LNOrderScene = .normal private var targetUid: String? var completionHandler: ((String) -> Void)? override init(frame: CGRect) { super.init(frame: frame) setupViews() LNEventDeliver.addObserver(self) LNPurchaseManager.shared.reloadWalletInfo() } func update(_ detail: LNGameMateSkillDetailVO, count: Int, extra: String) { gameNameLabel.text = detail.categoryName avatar.showAvatar(detail.avatar) nameLabel.text = detail.nickname priceLabel.text = "\(detail.price.toDisplay)/\(detail.unit)" if let discount = LNOrderManager.shared.discountFor(detail.price) { discountView.isHidden = false newbieView.update(1 - discount) discountCoinLabel.text = "-\(detail.price * (1 - discount))" } else { discountView.isHidden = true } skillId = detail.id price = detail.price targetUid = detail.userNo curCount = count extraInput.text = extra } func update(_ skill: LNGameMateSkillVO, user: LNUserProfileVO) { gameNameLabel.text = skill.name avatar.showAvatar(user.avatar) nameLabel.text = user.nickname priceLabel.text = "\(skill.price.toDisplay)/\(skill.unit)" if let discount = LNOrderManager.shared.discountFor(skill.price) { discountView.isHidden = false newbieView.update(1 - discount) discountCoinLabel.text = "-\(skill.price * (1 - discount))" } else { discountView.isHidden = true } skillId = skill.id price = skill.price targetUid = user.userNo curCount = 1 } func update(_ detail: LNQRCodeDetailResponse, count: Int, extra: String) { gameNameLabel.text = detail.bizCategoryName avatar.showAvatar(detail.avatar) nameLabel.text = detail.nickname priceLabel.text = "\(detail.price.toDisplay)/\(detail.unit)" if let discount = LNOrderManager.shared.discountFor(detail.price) { discountView.isHidden = false newbieView.update(1 - discount) discountCoinLabel.text = "-\(detail.price * (1 - discount))" } else { discountView.isHidden = true } qrCode = detail.qrCode price = detail.price targetUid = detail.sellerUserNo curCount = count extraInput.text = extra } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNCreateOrderPanel: LNPurchaseManagerNotify { func onUserWalletInfoChanged(info: LNUserWalletInfo) { curCoinLabel.text = info.coin.toDisplay } } extension LNCreateOrderPanel: 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.count < currentText.count { return true } return newText.count <= LNOrderManager.orderExtraMaxLength } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } } extension LNCreateOrderPanel { private func setupViews() { container.backgroundColor = .primary_1 let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 10 container.addSubview(stackView) stackView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(12) make.top.equalToSuperview().offset(20) } let billInfo = buildBillInfo() stackView.addArrangedSubview(billInfo) let extra = buildExtra() stackView.addArrangedSubview(extra) let wallet = buildWalletInfo() stackView.addArrangedSubview(wallet) let confirmButton = UIButton() confirmButton.setTitle(.init(key: "A00123"), for: .normal) confirmButton.setTitleColor(.text_1, for: .normal) confirmButton.titleLabel?.font = .heading_h3 confirmButton.setBackgroundImage(.primary_8, for: .normal) confirmButton.layer.cornerRadius = 23.5 confirmButton.clipsToBounds = true confirmButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } let extra = extraInput.text ?? "" let handler = { [weak self] (orderNo: String?) in guard let self else { return } guard let orderNo else { return } dismiss() if let targetUid, !(navigationController?.topViewController is LNIMChatViewController) { pushToChat(uid: targetUid) } completionHandler?(orderNo) } if let skillId { LNOrderManager.shared.createOrder( skillId: skillId, count: curCount, remark: extra, scene: scene, handler: handler) } else if let qrCode { LNOrderManager.shared.createQRCodeOrder( data: qrCode, count: curCount, extra: extra, scene: scene, handler: handler) } }), for: .touchUpInside) container.addSubview(confirmButton) confirmButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(12) make.top.equalTo(stackView.snp.bottom).offset(15) make.bottom.equalToSuperview().offset(commonBottomInset) make.height.equalTo(47) } } private func buildBillInfo() -> UIView { let container = UIView() container.backgroundColor = .fill container.layer.cornerRadius = 12 container.onTap { [weak self] in guard let self else { return } endEditing(true) } let stackView = UIStackView() stackView.axis = .vertical container.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() } stackView.addArrangedSubview(buildUserInfo()) stackView.addArrangedSubview(buildPrice()) stackView.addArrangedSubview(buildDiscount()) stackView.addArrangedSubview(buildCost()) return container } private func buildUserInfo() -> UIView { let container = UIView() container.isUserInteractionEnabled = false container.snp.makeConstraints { make in make.height.equalTo(83) } let skillTag = UIImageView() skillTag.image = .icOrderSkillBg container.addSubview(skillTag) skillTag.snp.makeConstraints { make in make.leading.top.equalToSuperview() } avatar.layer.cornerRadius = 30 avatar.layer.borderWidth = 2 avatar.layer.borderColor = UIColor.fill.cgColor avatar.backgroundColor = .fill avatar.clipsToBounds = true container.addSubview(avatar) avatar.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview().offset(-8) make.width.height.equalTo(60) } gameNameLabel.font = .heading_h5 gameNameLabel.textColor = .text_1 gameNameLabel.textAlignment = .center skillTag.addSubview(gameNameLabel) gameNameLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.horizontalEdges.equalToSuperview().inset(14) make.width.lessThanOrEqualTo(82) } nameLabel.font = .heading_h4 nameLabel.textColor = .text_5 container.addSubview(nameLabel) nameLabel.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalTo(avatar.snp.bottom).offset(3) } let line = UIView() line.backgroundColor = .fill_2 container.addSubview(line) line.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(12) make.height.equalTo(1) make.bottom.equalToSuperview() } return container } private func buildPrice() -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(49) } let titleLabel = UILabel() titleLabel.font = .heading_h5 titleLabel.textColor = .text_5 titleLabel.text = .init(key: "A00128") container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(12) make.centerY.equalToSuperview() } let coin = UIImageView.coinImageView() container.addSubview(coin) coin.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(titleLabel.snp.trailing).offset(6) make.width.height.equalTo(18) } priceLabel.font = .body_m priceLabel.textColor = .text_5 container.addSubview(priceLabel) priceLabel.snp.makeConstraints { make in make.leading.equalTo(coin.snp.trailing).offset(2) make.centerY.equalToSuperview() } countLabel.font = .body_m countLabel.textColor = .text_5 container.addSubview(countLabel) countLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } countEditView.isHidden = true container.addSubview(countEditView) countEditView.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.trailing.equalToSuperview() } addButton.setTitle("+", for: .normal) addButton.setTitleColor(.text_4, for: .normal) addButton.setTitleColor(.text_2, for: .disabled) addButton.backgroundColor = .primary_1 addButton.layer.cornerRadius = 12 addButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } curCount += 1 }), for: .touchUpInside) countEditView.addSubview(addButton) addButton.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-12) make.width.height.equalTo(24) } customCountLabel.font = .body_m customCountLabel.textColor = .text_5 countEditView.addSubview(customCountLabel) customCountLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(addButton.snp.leading).offset(-10) } minuButton.setTitle("-", for: .normal) minuButton.setTitleColor(.text_4, for: .normal) minuButton.setTitleColor(.text_2, for: .disabled) minuButton.backgroundColor = .primary_1 minuButton.layer.cornerRadius = 12 minuButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } self.curCount -= 1 }), for: .touchUpInside) countEditView.addSubview(minuButton) minuButton.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(customCountLabel.snp.leading).offset(-10) make.width.height.equalTo(24) make.leading.equalToSuperview() } return container } private func buildDiscount() -> UIView { discountView.snp.makeConstraints { make in make.height.equalTo(49) } let titleLabel = UILabel() titleLabel.font = .heading_h5 titleLabel.textColor = .text_5 titleLabel.text = .init(key: "A00129") discountView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(12) make.centerY.equalToSuperview() } discountView.addSubview(newbieView) newbieView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(titleLabel.snp.trailing).offset(8) } discountCoinLabel.font = .heading_h3 discountCoinLabel.textColor = .init(hex: "#FF6F32") discountView.addSubview(discountCoinLabel) discountCoinLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-12) } let coin = UIImageView.coinImageView() discountView.addSubview(coin) coin.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(discountCoinLabel.snp.leading).offset(-2) make.width.height.equalTo(20) } return discountView } private func buildCost() -> UIView { let container = UIView() container.isUserInteractionEnabled = false container.snp.makeConstraints { make in make.height.equalTo(49) } costLabel.font = .heading_h1 costLabel.textColor = .text_5 container.addSubview(costLabel) costLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-12) } let coin = UIImageView.coinImageView() container.addSubview(coin) coin.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(costLabel.snp.leading).offset(-2) make.width.height.equalTo(24) } let totalLabel = UILabel() totalLabel.text = .init(key: "A00124") totalLabel.font = .body_m totalLabel.textColor = .text_5 container.addSubview(totalLabel) totalLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(coin.snp.leading).offset(-2) } return container } private func buildExtra() -> UIView { extraView.isHidden = true extraView.backgroundColor = .fill extraView.layer.cornerRadius = 12 extraView.snp.makeConstraints { make in make.height.equalTo(50) } let titleLabel = UILabel() titleLabel.font = .heading_h5 titleLabel.text = .init(key: "A00086") titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) titleLabel.textColor = .text_5 extraView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(12) make.centerY.equalToSuperview() } extraInput.placeholder = .init(key: "A00125", LNOrderManager.orderExtraMaxLength) extraInput.font = .body_s extraInput.textColor = .text_5 extraInput.clearButtonMode = .whileEditing extraInput.delegate = self extraInput.visibleView = extraView extraView.addSubview(extraInput) extraInput.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(titleLabel.snp.trailing).offset(8) make.trailing.equalToSuperview().offset(-12) } return extraView } private func buildWalletInfo() -> UIView { let container = UIView() container.isUserInteractionEnabled = false container.backgroundColor = .fill container.layer.cornerRadius = 12 container.snp.makeConstraints { make in make.height.equalTo(66) } let icon = UIImageView() icon.image = .icWalletWithBg container.addSubview(icon) icon.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().offset(12) } let infoView = UIView() container.addSubview(infoView) infoView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(icon.snp.trailing).offset(6) } let titleLabel = UILabel() titleLabel.text = .init(key: "A00126") titleLabel.font = .heading_h5 titleLabel.textColor = .text_5 infoView.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.top.equalToSuperview() make.trailing.equalToSuperview() } curCoinLabel.text = myWalletInfo.coin.toDisplay curCoinLabel.font = .body_m curCoinLabel.textColor = .text_3 infoView.addSubview(curCoinLabel) curCoinLabel.snp.makeConstraints { make in make.trailing.bottom.equalToSuperview() make.top.equalTo(titleLabel.snp.bottom).offset(2) } let coin = UIImageView.coinImageView() infoView.addSubview(coin) coin.snp.makeConstraints { make in make.centerY.equalTo(curCoinLabel) make.leading.equalToSuperview() make.trailing.equalTo(curCoinLabel.snp.leading).offset(-2) make.width.height.equalTo(16) } let check = UIImageView() check.image = .icCheck container.addSubview(check) check.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-12) make.width.height.equalTo(22) } return container } } #if DEBUG import SwiftUI struct LNCreateOrderPanelPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .lightGray let view = LNCreateOrderPanel() view.popup(container) return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNCreateOrderPanelPreview() }) #endif // DEBUG