LNOrderProtestViewController.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. //
  2. // LNOrderProtestViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/24.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. extension UIView {
  11. func pushToProtest(_ orderId: String, handler: (() -> Void)? = nil) {
  12. let vc = LNOrderProtestViewController(orderId: orderId, handler: handler)
  13. navigationController?.pushViewController(vc, animated: true)
  14. }
  15. }
  16. class LNOrderProtestViewController: LNViewController {
  17. private let orderId: String
  18. private let finishHandler: (() -> Void)?
  19. private var curDetail: LNOrderDetailResponse?
  20. private let orderIdLabel = UILabel()
  21. private let stateLabel = UILabel()
  22. private let nameLabel = UILabel()
  23. private let gameLabel = UILabel()
  24. private let priceLabel = UILabel()
  25. private let countLabel = UILabel()
  26. private let totalLabel = UILabel()
  27. private let refundView = LNOrderRefundInfoView()
  28. private let maxUpload = 6
  29. private let messageInputView = LNCommonTextView()
  30. private let imageUploadView = LNMultiImagesUploadView()
  31. private let submitButton = UIButton()
  32. init(orderId: String, handler: (() -> Void)?) {
  33. self.orderId = orderId
  34. finishHandler = handler
  35. super.init(nibName: nil, bundle: nil)
  36. }
  37. override func viewDidLoad() {
  38. super.viewDidLoad()
  39. setupViews()
  40. updateSubmitButton()
  41. reloadDetail()
  42. }
  43. required init?(coder: NSCoder) {
  44. fatalError("init(coder:) has not been implemented")
  45. }
  46. }
  47. extension LNOrderProtestViewController: LNOrderManagerNotify {
  48. func onOrderInfoChanged(orderId: String) {
  49. guard orderId == self.orderId else { return }
  50. reloadDetail()
  51. }
  52. }
  53. extension LNOrderProtestViewController {
  54. private func reloadDetail() {
  55. LNOrderManager.shared.getOrderDetail(orderId: orderId) { [weak self] detail in
  56. guard let self else { return }
  57. guard let detail else { return }
  58. curDetail = detail
  59. update(detail)
  60. }
  61. }
  62. private func update(_ item: LNOrderDetailResponse) {
  63. orderIdLabel.text = item.orderInfo.orderId
  64. nameLabel.text = item.orderInfo.nickname
  65. gameLabel.text = item.orderInfo.bizCategoryName
  66. priceLabel.text = item.orderInfo.price.toDisplay
  67. countLabel.text = "\(item.orderInfo.unit)x\(item.orderInfo.purchaseQty)"
  68. totalLabel.text = "\((item.orderInfo.price * Double(item.orderInfo.purchaseQty)).toDisplay)"
  69. refundView.update(item)
  70. }
  71. }
  72. extension LNOrderProtestViewController: UITextViewDelegate {
  73. func textViewDidChange(_ textView: UITextView) {
  74. updateSubmitButton()
  75. }
  76. }
  77. extension LNOrderProtestViewController {
  78. private func updateSubmitButton() {
  79. if !messageInputView.text.isEmpty {
  80. submitButton.backgroundColor = .clear
  81. submitButton.setTitleColor(.text_5, for: .normal)
  82. submitButton.isEnabled = true
  83. } else {
  84. submitButton.backgroundColor = .fill_4
  85. submitButton.setTitleColor(.text_1, for: .normal)
  86. submitButton.isEnabled = false
  87. }
  88. }
  89. }
  90. extension LNOrderProtestViewController {
  91. private func setupViews() {
  92. view.backgroundColor = .primary_1
  93. title = .init(key: "A00172")
  94. submitButton.setTitle(.init(key: "A00170"), for: .normal)
  95. submitButton.layer.cornerRadius = 23.5
  96. submitButton.layer.borderWidth = 1
  97. submitButton.layer.borderColor = UIColor.text_2.cgColor
  98. submitButton.addAction(UIAction(handler: { [weak self] _ in
  99. guard let self else { return }
  100. let reason = messageInputView.text
  101. let attachments = imageUploadView.curFileUrls
  102. LNOrderManager.shared.protestOrder(
  103. orderId: orderId, reason: reason, attachments: attachments)
  104. { [weak self] success in
  105. guard let self else { return }
  106. guard success else { return }
  107. navigationController?.popViewController(animated: true)
  108. }
  109. }), for: .touchUpInside)
  110. view.addSubview(submitButton)
  111. submitButton.snp.makeConstraints { make in
  112. make.horizontalEdges.equalToSuperview().inset(16)
  113. make.bottom.equalToSuperview().offset(-view.safeBottomInset - 10)
  114. make.height.equalTo(47)
  115. }
  116. let scrollView = UIScrollView()
  117. scrollView.showsVerticalScrollIndicator = false
  118. scrollView.showsHorizontalScrollIndicator = false
  119. view.addSubview(scrollView)
  120. scrollView.snp.makeConstraints { make in
  121. make.horizontalEdges.equalToSuperview()
  122. make.top.equalToSuperview()
  123. make.bottom.equalTo(submitButton.snp.top)
  124. }
  125. let fakeView = UIView()
  126. scrollView.addSubview(fakeView)
  127. fakeView.snp.makeConstraints { make in
  128. make.horizontalEdges.equalToSuperview()
  129. make.top.equalToSuperview()
  130. make.width.equalToSuperview()
  131. make.height.equalTo(0)
  132. }
  133. let infoView = buildInfoView()
  134. scrollView.addSubview(infoView)
  135. infoView.snp.makeConstraints { make in
  136. make.horizontalEdges.equalToSuperview().inset(16)
  137. make.top.equalToSuperview().offset(10)
  138. }
  139. let inputView = buildInputView()
  140. scrollView.addSubview(inputView)
  141. inputView.snp.makeConstraints { make in
  142. make.horizontalEdges.equalToSuperview().inset(16)
  143. make.top.equalTo(infoView.snp.bottom).offset(10)
  144. make.bottom.equalToSuperview().offset(-16)
  145. }
  146. view.onTap { [weak self] in
  147. guard let self else { return }
  148. view.endEditing(true)
  149. }
  150. }
  151. private func buildInfoView() -> UIView {
  152. let container = UIView()
  153. container.layer.cornerRadius = 12
  154. container.backgroundColor = .fill
  155. let stackView = UIStackView()
  156. stackView.axis = .vertical
  157. stackView.spacing = 6
  158. container.addSubview(stackView)
  159. stackView.snp.makeConstraints { make in
  160. make.horizontalEdges.equalToSuperview().inset(12)
  161. make.top.equalToSuperview().offset(2)
  162. }
  163. stackView.addArrangedSubview(buildOrderDesc())
  164. nameLabel.font = .body_s
  165. nameLabel.textColor = .text_5
  166. let userInfo = buildDetailInfo(title: .init(key: "A00173"), contentView: nameLabel)
  167. stackView.addArrangedSubview(userInfo)
  168. gameLabel.font = .body_s
  169. gameLabel.textColor = .text_5
  170. let gameInfo = buildDetailInfo(title: .init(key: "A00174"), contentView: gameLabel)
  171. stackView.addArrangedSubview(gameInfo)
  172. let priceView = buildPrice()
  173. stackView.addArrangedSubview(priceView)
  174. countLabel.font = .body_s
  175. countLabel.textColor = .text_5
  176. let countView = buildDetailInfo(title: .init(key: "A00122"), contentView: countLabel)
  177. stackView.addArrangedSubview(countView)
  178. stackView.addArrangedSubview(buildTotal())
  179. let line = buildLine()
  180. container.addSubview(line)
  181. line.snp.makeConstraints { make in
  182. make.horizontalEdges.equalToSuperview()
  183. make.top.equalTo(stackView.snp.bottom).offset(10)
  184. }
  185. container.addSubview(refundView)
  186. refundView.snp.makeConstraints { make in
  187. make.horizontalEdges.equalToSuperview()
  188. make.top.equalTo(line.snp.bottom).offset(10)
  189. make.bottom.equalToSuperview().offset(-12)
  190. }
  191. return container
  192. }
  193. private func buildOrderDesc() -> UIView {
  194. let container = UIView()
  195. container.snp.makeConstraints { make in
  196. make.height.equalTo(30)
  197. }
  198. stateLabel.text = .init(key: "A00052")
  199. stateLabel.font = .heading_h5
  200. stateLabel.textColor = .init(hex: "#F23051")
  201. container.addSubview(stateLabel)
  202. stateLabel.snp.makeConstraints { make in
  203. make.centerY.equalToSuperview()
  204. make.trailing.equalToSuperview()
  205. }
  206. orderIdLabel.font = .body_xs
  207. orderIdLabel.textColor = .text_5
  208. container.addSubview(orderIdLabel)
  209. orderIdLabel.snp.makeConstraints { make in
  210. make.leading.equalToSuperview()
  211. make.centerY.equalToSuperview()
  212. make.trailing.lessThanOrEqualTo(stateLabel.snp.leading).offset(-4)
  213. }
  214. return container
  215. }
  216. private func buildLine() -> UIView {
  217. let container = UIView()
  218. container.snp.makeConstraints { make in
  219. make.height.equalTo(0.5)
  220. }
  221. let line = UIView()
  222. line.backgroundColor = .fill_2
  223. container.addSubview(line)
  224. line.snp.makeConstraints { make in
  225. make.horizontalEdges.equalToSuperview().inset(10)
  226. make.centerY.equalToSuperview()
  227. make.height.equalTo(0.5)
  228. }
  229. return container
  230. }
  231. private func buildDetailInfo(title: String, contentView: UIView) -> UIView {
  232. let container = UIView()
  233. container.snp.makeConstraints { make in
  234. make.height.equalTo(18)
  235. }
  236. let titleLabel = UILabel()
  237. titleLabel.font = .body_s
  238. titleLabel.textColor = .text_5
  239. titleLabel.text = title
  240. container.addSubview(titleLabel)
  241. titleLabel.snp.makeConstraints { make in
  242. make.leading.equalToSuperview()
  243. make.centerY.equalToSuperview()
  244. }
  245. container.addSubview(contentView)
  246. contentView.snp.makeConstraints { make in
  247. make.centerY.equalToSuperview()
  248. make.trailing.equalToSuperview().offset(-12)
  249. make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing)
  250. }
  251. return container
  252. }
  253. private func buildPrice() -> UIView {
  254. let priceView = UIView()
  255. let coin = UIImageView.coinImageView()
  256. priceView.addSubview(coin)
  257. coin.snp.makeConstraints { make in
  258. make.leading.centerY.equalToSuperview()
  259. make.width.height.equalTo(14)
  260. }
  261. priceLabel.font = .body_s
  262. priceLabel.textColor = .text_5
  263. priceView.addSubview(priceLabel)
  264. priceLabel.snp.makeConstraints { make in
  265. make.verticalEdges.equalToSuperview()
  266. make.trailing.equalToSuperview()
  267. make.leading.equalTo(coin.snp.trailing)
  268. }
  269. return buildDetailInfo(title: .init(key: "A00128"), contentView: priceView)
  270. }
  271. private func buildTotal() -> UIView {
  272. let container = UIView()
  273. container.snp.makeConstraints { make in
  274. make.height.equalTo(38)
  275. }
  276. totalLabel.font = .heading_h3
  277. totalLabel.textColor = .text_5
  278. container.addSubview(totalLabel)
  279. totalLabel.snp.makeConstraints { make in
  280. make.centerY.equalToSuperview()
  281. make.trailing.equalToSuperview().offset(-12)
  282. }
  283. let coin = UIImageView.coinImageView()
  284. container.addSubview(coin)
  285. coin.snp.makeConstraints { make in
  286. make.centerY.equalToSuperview()
  287. make.trailing.equalTo(totalLabel.snp.leading)
  288. make.width.height.equalTo(18)
  289. }
  290. let text = UILabel()
  291. text.font = .body_s
  292. text.textColor = .text_5
  293. text.text = .init(key: "A00124")
  294. container.addSubview(text)
  295. text.snp.makeConstraints { make in
  296. make.centerY.equalToSuperview()
  297. make.trailing.equalTo(coin.snp.leading)
  298. }
  299. return container
  300. }
  301. private func buildInputView() -> UIView {
  302. let container = UIView()
  303. container.backgroundColor = .fill
  304. container.layer.cornerRadius = 12
  305. let textView = buildTextView()
  306. container.addSubview(textView)
  307. textView.snp.makeConstraints { make in
  308. make.horizontalEdges.equalToSuperview()
  309. make.top.equalToSuperview().offset(16)
  310. }
  311. let photoView = buildPhotoView()
  312. container.addSubview(photoView)
  313. photoView.snp.makeConstraints { make in
  314. make.horizontalEdges.equalToSuperview()
  315. make.top.equalTo(textView.snp.bottom).offset(16)
  316. make.bottom.equalToSuperview().offset(-16)
  317. }
  318. return container
  319. }
  320. private func buildTextView() -> UIView {
  321. let container = UIView()
  322. let titleLabel = UILabel()
  323. titleLabel.text = .init(key: "A00175")
  324. titleLabel.font = .heading_h4
  325. titleLabel.textColor = .text_5
  326. titleLabel.numberOfLines = 0
  327. container.addSubview(titleLabel)
  328. titleLabel.snp.makeConstraints { make in
  329. make.top.equalToSuperview()
  330. make.horizontalEdges.equalToSuperview().inset(16)
  331. }
  332. let descLabel = UILabel()
  333. descLabel.text = .init(key: "A00176")
  334. descLabel.font = .body_s
  335. descLabel.textColor = .text_4
  336. descLabel.numberOfLines = 0
  337. container.addSubview(descLabel)
  338. descLabel.snp.makeConstraints { make in
  339. make.top.equalTo(titleLabel.snp.bottom).offset(2)
  340. make.horizontalEdges.equalToSuperview().inset(16)
  341. }
  342. messageInputView.delegate = self
  343. messageInputView.maxInput = LNOrderManager.orderProtestMaxLength
  344. container.addSubview(messageInputView)
  345. messageInputView.snp.makeConstraints { make in
  346. make.horizontalEdges.equalToSuperview().inset(16)
  347. make.top.equalTo(descLabel.snp.bottom).offset(8)
  348. make.bottom.equalToSuperview()
  349. make.height.equalTo(150)
  350. }
  351. return container
  352. }
  353. private func buildPhotoView() -> UIView {
  354. let container = UIView()
  355. let titleLabel = UILabel()
  356. titleLabel.text = .init(key: "A00177", maxUpload)
  357. titleLabel.font = .heading_h4
  358. titleLabel.textColor = .text_5
  359. container.addSubview(titleLabel)
  360. titleLabel.snp.makeConstraints { make in
  361. make.top.equalToSuperview().offset(16)
  362. make.horizontalEdges.equalToSuperview().inset(16)
  363. }
  364. let descLabel = UILabel()
  365. descLabel.text = .init(key: "A00178")
  366. descLabel.font = .body_s
  367. descLabel.textColor = .text_4
  368. container.addSubview(descLabel)
  369. descLabel.snp.makeConstraints { make in
  370. make.horizontalEdges.equalToSuperview().inset(16)
  371. make.top.equalTo(titleLabel.snp.bottom).offset(2)
  372. }
  373. imageUploadView.maxPhoto = maxUpload
  374. container.addSubview(imageUploadView)
  375. imageUploadView.snp.makeConstraints { make in
  376. make.horizontalEdges.equalToSuperview().inset(16)
  377. make.top.equalTo(descLabel.snp.bottom).offset(16)
  378. make.bottom.equalToSuperview()
  379. make.height.equalTo(0).priority(.low)
  380. }
  381. return container
  382. }
  383. }
  384. #if DEBUG
  385. import SwiftUI
  386. struct LNOrderRefundSummitViewControllerPreview: UIViewControllerRepresentable {
  387. func makeUIViewController(context: Context) -> some UIViewController {
  388. LNOrderProtestViewController(orderId: "", handler: nil)
  389. }
  390. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
  391. }
  392. #Preview(body: {
  393. LNOrderRefundSummitViewControllerPreview()
  394. })
  395. #endif