// // LNOrderProtestViewController.swift // Lanu // // Created by OneeChan on 2025/12/24. // import Foundation import UIKit import SnapKit extension UIView { func pushToProtest(_ orderId: String, handler: (() -> Void)? = nil) { let vc = LNOrderProtestViewController(orderId: orderId, handler: handler) navigationController?.pushViewController(vc, animated: true) } } class LNOrderProtestViewController: LNViewController { private let orderId: String private let finishHandler: (() -> Void)? private var curDetail: LNOrderDetailResponse? private let orderIdLabel = UILabel() private let stateLabel = UILabel() private let nameLabel = UILabel() private let gameLabel = UILabel() private let priceLabel = UILabel() private let countLabel = UILabel() private let totalLabel = UILabel() private let refundView = LNOrderRefundInfoView() private let maxUpload = 6 private let messageInputView = LNCommonTextView() private let imageUploadView = LNMultiImagesUploadView() private let submitButton = UIButton() init(orderId: String, handler: (() -> Void)?) { self.orderId = orderId finishHandler = handler super.init(nibName: nil, bundle: nil) } override func viewDidLoad() { super.viewDidLoad() setupViews() updateSubmitButton() reloadDetail() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNOrderProtestViewController: LNOrderManagerNotify { func onOrderInfoChanged(orderId: String) { guard orderId == self.orderId else { return } reloadDetail() } } extension LNOrderProtestViewController { private func reloadDetail() { LNOrderManager.shared.getOrderDetail(orderId: orderId) { [weak self] detail in guard let self else { return } guard let detail else { return } curDetail = detail update(detail) } } private func update(_ item: LNOrderDetailResponse) { orderIdLabel.text = item.orderInfo.orderId nameLabel.text = item.orderInfo.nickname gameLabel.text = item.orderInfo.bizCategoryName priceLabel.text = item.orderInfo.price.toDisplay countLabel.text = "\(item.orderInfo.unit)x\(item.orderInfo.purchaseQty)" totalLabel.text = "\((item.orderInfo.price * Double(item.orderInfo.purchaseQty)).toDisplay)" refundView.update(item) } } extension LNOrderProtestViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { updateSubmitButton() } } extension LNOrderProtestViewController { private func updateSubmitButton() { if !messageInputView.text.isEmpty { submitButton.backgroundColor = .clear submitButton.setTitleColor(.text_5, for: .normal) submitButton.isEnabled = true } else { submitButton.backgroundColor = .fill_4 submitButton.setTitleColor(.text_1, for: .normal) submitButton.isEnabled = false } } } extension LNOrderProtestViewController { private func setupViews() { view.backgroundColor = .primary_1 title = .init(key: "A00172") submitButton.setTitle(.init(key: "A00170"), for: .normal) submitButton.layer.cornerRadius = 23.5 submitButton.layer.borderWidth = 1 submitButton.layer.borderColor = UIColor.text_2.cgColor submitButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } let reason = messageInputView.text let attachments = imageUploadView.curFileUrls LNOrderManager.shared.protestOrder( orderId: orderId, reason: reason, attachments: attachments) { [weak self] success in guard let self else { return } guard success else { return } navigationController?.popViewController(animated: true) } }), for: .touchUpInside) view.addSubview(submitButton) submitButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.bottom.equalToSuperview().offset(-view.safeBottomInset - 10) make.height.equalTo(47) } let scrollView = UIScrollView() scrollView.showsVerticalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false view.addSubview(scrollView) scrollView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview() make.bottom.equalTo(submitButton.snp.top) } let fakeView = UIView() scrollView.addSubview(fakeView) fakeView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview() make.width.equalToSuperview() make.height.equalTo(0) } let infoView = buildInfoView() scrollView.addSubview(infoView) infoView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalToSuperview().offset(10) } let inputView = buildInputView() scrollView.addSubview(inputView) inputView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(infoView.snp.bottom).offset(10) make.bottom.equalToSuperview().offset(-16) } view.onTap { [weak self] in guard let self else { return } view.endEditing(true) } } private func buildInfoView() -> UIView { let container = UIView() container.layer.cornerRadius = 12 container.backgroundColor = .fill let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 6 container.addSubview(stackView) stackView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(12) make.top.equalToSuperview().offset(2) } stackView.addArrangedSubview(buildOrderDesc()) nameLabel.font = .body_s nameLabel.textColor = .text_5 let userInfo = buildDetailInfo(title: .init(key: "A00173"), contentView: nameLabel) stackView.addArrangedSubview(userInfo) gameLabel.font = .body_s gameLabel.textColor = .text_5 let gameInfo = buildDetailInfo(title: .init(key: "A00174"), contentView: gameLabel) stackView.addArrangedSubview(gameInfo) let priceView = buildPrice() stackView.addArrangedSubview(priceView) countLabel.font = .body_s countLabel.textColor = .text_5 let countView = buildDetailInfo(title: .init(key: "A00122"), contentView: countLabel) stackView.addArrangedSubview(countView) stackView.addArrangedSubview(buildTotal()) let line = buildLine() container.addSubview(line) line.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(stackView.snp.bottom).offset(10) } container.addSubview(refundView) refundView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(line.snp.bottom).offset(10) make.bottom.equalToSuperview().offset(-12) } return container } private func buildOrderDesc() -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(30) } stateLabel.text = .init(key: "A00052") stateLabel.font = .heading_h5 stateLabel.textColor = .init(hex: "#F23051") container.addSubview(stateLabel) stateLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview() } orderIdLabel.font = .body_xs orderIdLabel.textColor = .text_5 container.addSubview(orderIdLabel) orderIdLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() make.trailing.lessThanOrEqualTo(stateLabel.snp.leading).offset(-4) } return container } private func buildLine() -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(0.5) } let line = UIView() line.backgroundColor = .fill_2 container.addSubview(line) line.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(10) make.centerY.equalToSuperview() make.height.equalTo(0.5) } return container } private func buildDetailInfo(title: String, contentView: UIView) -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(18) } let titleLabel = UILabel() titleLabel.font = .body_s titleLabel.textColor = .text_5 titleLabel.text = title container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.leading.equalToSuperview() make.centerY.equalToSuperview() } container.addSubview(contentView) contentView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-12) make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing) } return container } private func buildPrice() -> UIView { let priceView = UIView() let coin = UIImageView.coinImageView() priceView.addSubview(coin) coin.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() make.width.height.equalTo(14) } priceLabel.font = .body_s priceLabel.textColor = .text_5 priceView.addSubview(priceLabel) priceLabel.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.trailing.equalToSuperview() make.leading.equalTo(coin.snp.trailing) } return buildDetailInfo(title: .init(key: "A00128"), contentView: priceView) } private func buildTotal() -> UIView { let container = UIView() container.snp.makeConstraints { make in make.height.equalTo(38) } totalLabel.font = .heading_h3 totalLabel.textColor = .text_5 container.addSubview(totalLabel) totalLabel.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(totalLabel.snp.leading) make.width.height.equalTo(18) } let text = UILabel() text.font = .body_s text.textColor = .text_5 text.text = .init(key: "A00124") container.addSubview(text) text.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(coin.snp.leading) } return container } private func buildInputView() -> UIView { let container = UIView() container.backgroundColor = .fill container.layer.cornerRadius = 12 let textView = buildTextView() container.addSubview(textView) textView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview().offset(16) } let photoView = buildPhotoView() container.addSubview(photoView) photoView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(textView.snp.bottom).offset(16) make.bottom.equalToSuperview().offset(-16) } return container } private func buildTextView() -> UIView { let container = UIView() let titleLabel = UILabel() titleLabel.text = .init(key: "A00175") titleLabel.font = .heading_h4 titleLabel.textColor = .text_5 titleLabel.numberOfLines = 0 container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview() make.horizontalEdges.equalToSuperview().inset(16) } let descLabel = UILabel() descLabel.text = .init(key: "A00176") descLabel.font = .body_s descLabel.textColor = .text_4 descLabel.numberOfLines = 0 container.addSubview(descLabel) descLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(2) make.horizontalEdges.equalToSuperview().inset(16) } messageInputView.delegate = self messageInputView.maxInput = LNOrderManager.orderProtestMaxLength container.addSubview(messageInputView) messageInputView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(descLabel.snp.bottom).offset(8) make.bottom.equalToSuperview() make.height.equalTo(150) } return container } private func buildPhotoView() -> UIView { let container = UIView() let titleLabel = UILabel() titleLabel.text = .init(key: "A00177", maxUpload) titleLabel.font = .heading_h4 titleLabel.textColor = .text_5 container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().offset(16) make.horizontalEdges.equalToSuperview().inset(16) } let descLabel = UILabel() descLabel.text = .init(key: "A00178") descLabel.font = .body_s descLabel.textColor = .text_4 container.addSubview(descLabel) descLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(titleLabel.snp.bottom).offset(2) } imageUploadView.maxPhoto = maxUpload container.addSubview(imageUploadView) imageUploadView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(descLabel.snp.bottom).offset(16) make.bottom.equalToSuperview() make.height.equalTo(0).priority(.low) } return container } } #if DEBUG import SwiftUI struct LNOrderRefundSummitViewControllerPreview: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { LNOrderProtestViewController(orderId: "", handler: nil) } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } #Preview(body: { LNOrderRefundSummitViewControllerPreview() }) #endif