LNCreateOrderViewController.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. //
  2. // LNCreateOrderViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/22.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. extension UIView {
  11. func pushToCreateOrder(_ skillDetail: LNGameMateSkillDetailVO) {
  12. let vc = LNCreateOrderViewController()
  13. vc.loadSkill(skillDetail)
  14. navigationController?.pushViewController(vc, animated: true)
  15. }
  16. func pushToCreateOrder(_ qrCode: LNQRCodeDetailResponse) {
  17. let vc = LNCreateOrderViewController()
  18. vc.loadQRData(qrCode)
  19. navigationController?.pushViewController(vc, animated: true)
  20. }
  21. }
  22. class LNCreateOrderViewController: LNViewController {
  23. private var skill: LNGameMateSkillDetailVO?
  24. private var qrDetail: LNQRCodeDetailResponse?
  25. private let avatar = UIImageView()
  26. private let nameLabel = UILabel()
  27. private let starLabel = UILabel()
  28. private let skillIc = UIImageView()
  29. private let skillNameLabel = UILabel()
  30. private let skillPriceLabel = UILabel()
  31. private let countLabel = UILabel()
  32. private let minuButton = UIButton()
  33. private let addButton = UIButton()
  34. private var curCount = 1 {
  35. didSet {
  36. countLabel.text = "\(curCount)"
  37. if curCount <= 1 {
  38. minuButton.isEnabled = false
  39. } else {
  40. minuButton.isEnabled = true
  41. }
  42. updateCost()
  43. }
  44. }
  45. private let extraInput = UITextField()
  46. private let costLabel = UILabel()
  47. private let orderButton = UIButton()
  48. override func viewDidLoad() {
  49. super.viewDidLoad()
  50. setupViews()
  51. }
  52. func loadSkill(_ detail: LNGameMateSkillDetailVO) {
  53. skill = detail
  54. updateContent(detail)
  55. }
  56. func loadQRData(_ data: LNQRCodeDetailResponse) {
  57. qrDetail = data
  58. updateContent(data)
  59. }
  60. }
  61. extension LNCreateOrderViewController: UITextFieldDelegate {
  62. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  63. let currentText = textField.text ?? ""
  64. guard let range = Range(range, in: currentText) else { return false }
  65. let newText = currentText.replacingCharacters(in: range, with: string)
  66. return newText.count <= LNOrderManager.orderExtraMaxLength
  67. }
  68. }
  69. extension LNCreateOrderViewController {
  70. private func updateContent(_ detail: LNGameMateSkillDetailVO) {
  71. avatar.sd_setImage(with: URL(string: detail.avatar))
  72. nameLabel.text = detail.nickname
  73. starLabel.text = "\(detail.star)"
  74. let skill = LNGameMateManager.shared.gameCategory(for: detail.categoryCode)
  75. skillIc.sd_setImage(with: URL(string: skill?.icon ?? ""))
  76. skillNameLabel.text = detail.categoryName
  77. skillPriceLabel.text = "\(detail.price.toDisplay)"
  78. curCount = 1
  79. orderButton.isEnabled = true
  80. }
  81. private func updateContent(_ detail: LNQRCodeDetailResponse) {
  82. avatar.sd_setImage(with: URL(string: detail.avatar))
  83. nameLabel.text = detail.nickname
  84. starLabel.text = "\(detail.star)"
  85. skillIc.sd_setImage(with: URL(string: detail.bizCategoryIcon))
  86. skillNameLabel.text = detail.bizCategoryName
  87. skillPriceLabel.text = "\(detail.price.toDisplay)"
  88. curCount = detail.purchaseQty
  89. if detail.type != .normal {
  90. minuButton.isEnabled = false
  91. addButton.isEnabled = false
  92. }
  93. orderButton.isEnabled = !detail.sellerUserNo.isMyUid
  94. }
  95. private func updateCost() {
  96. if let skill {
  97. let cost = skill.price * Double(curCount)
  98. let text: String = .init(key: "A00121", cost.toDisplay, curCount, skill.unit)
  99. let attrStr = NSMutableAttributedString(string: text, attributes: [.font: UIFont.body_s])
  100. let range = (text as NSString).range(of: "\(cost.toDisplay)")
  101. attrStr.addAttribute(.font, value: UIFont.heading_h2, range: range)
  102. costLabel.attributedText = attrStr
  103. } else if let qrDetail {
  104. let cost = qrDetail.price * Double(curCount)
  105. let text: String = .init(key: "A00121", cost.toDisplay, curCount, qrDetail.unit)
  106. let attrStr = NSMutableAttributedString(string: text, attributes: [.font: UIFont.body_s])
  107. let range = (text as NSString).range(of: "\(cost.toDisplay)")
  108. attrStr.addAttribute(.font, value: UIFont.heading_h2, range: range)
  109. costLabel.attributedText = attrStr
  110. }
  111. }
  112. private func setupViews() {
  113. title = .init(key: "A00127")
  114. view.backgroundColor = .primary_1
  115. view.onTap { [weak self] in
  116. guard let self else { return }
  117. view.endEditing(true)
  118. }
  119. let stackView = UIStackView()
  120. stackView.axis = .vertical
  121. stackView.spacing = 10
  122. view.addSubview(stackView)
  123. stackView.snp.makeConstraints { make in
  124. make.directionalHorizontalEdges.equalToSuperview().inset(16)
  125. make.top.equalToSuperview().offset(16)
  126. }
  127. let userInfo = buildUserInfo()
  128. stackView.addArrangedSubview(userInfo)
  129. let skillInfo = buildSkillInfo()
  130. stackView.addArrangedSubview(skillInfo)
  131. let extra = buildExtra()
  132. stackView.addArrangedSubview(extra)
  133. let menu = buildMenu()
  134. view.addSubview(menu)
  135. menu.snp.makeConstraints { make in
  136. make.directionalHorizontalEdges.equalToSuperview()
  137. make.bottom.equalToSuperview()
  138. }
  139. }
  140. private func buildUserInfo() -> UIView {
  141. let container = UIView()
  142. container.backgroundColor = .fill
  143. container.layer.cornerRadius = 12
  144. container.snp.makeConstraints { make in
  145. make.height.equalTo(60)
  146. }
  147. let starView = UIView()
  148. container.addSubview(starView)
  149. starView.snp.makeConstraints { make in
  150. make.centerY.equalToSuperview()
  151. make.trailing.equalToSuperview().offset(-12)
  152. }
  153. let star = UIImageView()
  154. star.image = .init(named: "ic_star_fill")
  155. starView.addSubview(star)
  156. star.snp.makeConstraints { make in
  157. make.centerY.equalToSuperview()
  158. make.leading.equalToSuperview()
  159. make.width.height.equalTo(16)
  160. }
  161. starLabel.font = .heading_h3
  162. starLabel.textColor = .text_5
  163. starView.addSubview(starLabel)
  164. starLabel.snp.makeConstraints { make in
  165. make.verticalEdges.equalToSuperview()
  166. make.leading.equalTo(star.snp.trailing).offset(4)
  167. make.trailing.equalToSuperview()
  168. }
  169. avatar.layer.cornerRadius = 20
  170. avatar.clipsToBounds = true
  171. container.addSubview(avatar)
  172. avatar.snp.makeConstraints { make in
  173. make.leading.equalToSuperview().offset(12)
  174. make.centerY.equalToSuperview()
  175. make.width.height.equalTo(40)
  176. }
  177. nameLabel.font = .heading_h4
  178. nameLabel.textColor = .text_5
  179. container.addSubview(nameLabel)
  180. nameLabel.snp.makeConstraints { make in
  181. make.leading.equalTo(avatar.snp.trailing).offset(8)
  182. make.centerY.equalToSuperview()
  183. make.trailing.lessThanOrEqualTo(starView.snp.leading).offset(-12)
  184. }
  185. return container
  186. }
  187. private func buildSkillInfo() -> UIView {
  188. let container = UIStackView()
  189. container.axis = .vertical
  190. container.spacing = 0
  191. container.backgroundColor = .fill
  192. container.layer.cornerRadius = 12
  193. let infoView = UIView()
  194. infoView.snp.makeConstraints { make in
  195. make.height.equalTo(40)
  196. }
  197. container.addArrangedSubview(infoView)
  198. skillIc.layer.cornerRadius = 13
  199. skillIc.clipsToBounds = true
  200. infoView.addSubview(skillIc)
  201. skillIc.snp.makeConstraints { make in
  202. make.leading.equalToSuperview().offset(12)
  203. make.centerY.equalToSuperview()
  204. make.width.height.equalTo(26)
  205. }
  206. skillNameLabel.font = .heading_h5
  207. skillNameLabel.textColor = .text_5
  208. infoView.addSubview(skillNameLabel)
  209. skillNameLabel.snp.makeConstraints { make in
  210. make.centerY.equalToSuperview()
  211. make.leading.equalTo(skillIc.snp.trailing).offset(2)
  212. }
  213. let line = UIView()
  214. line.backgroundColor = .fill_2
  215. infoView.addSubview(line)
  216. line.snp.makeConstraints { make in
  217. make.directionalHorizontalEdges.equalToSuperview().inset(11)
  218. make.bottom.equalToSuperview()
  219. make.height.equalTo(1)
  220. }
  221. let priceView = UIView()
  222. priceView.snp.makeConstraints { make in
  223. make.height.equalTo(38)
  224. }
  225. container.addArrangedSubview(priceView)
  226. let priceTitleLabel = UILabel()
  227. priceTitleLabel.font = .heading_h5
  228. priceTitleLabel.textColor = .text_5
  229. priceTitleLabel.text = .init(key: "A00128")
  230. priceView.addSubview(priceTitleLabel)
  231. priceTitleLabel.snp.makeConstraints { make in
  232. make.leading.equalToSuperview().offset(12)
  233. make.centerY.equalToSuperview()
  234. }
  235. skillPriceLabel.font = .heading_h3
  236. skillPriceLabel.textColor = .text_5
  237. priceView.addSubview(skillPriceLabel)
  238. skillPriceLabel.snp.makeConstraints { make in
  239. make.centerY.equalToSuperview()
  240. make.trailing.equalToSuperview().offset(-12)
  241. }
  242. let coinIc = UIImageView.coinImageView()
  243. priceView.addSubview(coinIc)
  244. coinIc.snp.makeConstraints { make in
  245. make.centerY.equalToSuperview()
  246. make.trailing.equalTo(skillPriceLabel.snp.leading).offset(-2)
  247. make.width.height.equalTo(20)
  248. }
  249. let countView = UIView()
  250. countView.snp.makeConstraints { make in
  251. make.height.equalTo(38)
  252. }
  253. container.addArrangedSubview(countView)
  254. let countTitleLabel = UILabel()
  255. countTitleLabel.font = .heading_h5
  256. countTitleLabel.textColor = .text_5
  257. countTitleLabel.text = .init(key: "A00122")
  258. countView.addSubview(countTitleLabel)
  259. countTitleLabel.snp.makeConstraints { make in
  260. make.leading.equalToSuperview().offset(12)
  261. make.centerY.equalToSuperview()
  262. }
  263. addButton.setTitle("+", for: .normal)
  264. addButton.setTitleColor(.text_4, for: .normal)
  265. addButton.setTitleColor(.text_2, for: .disabled)
  266. addButton.backgroundColor = .primary_1
  267. addButton.layer.cornerRadius = 12
  268. addButton.addAction(UIAction(handler: { [weak self] _ in
  269. guard let self else { return }
  270. curCount += 1
  271. }), for: .touchUpInside)
  272. countView.addSubview(addButton)
  273. addButton.snp.makeConstraints { make in
  274. make.centerY.equalToSuperview()
  275. make.trailing.equalToSuperview().offset(-12)
  276. make.width.height.equalTo(24)
  277. }
  278. countLabel.font = .body_m
  279. countLabel.textColor = .text_5
  280. countView.addSubview(countLabel)
  281. countLabel.snp.makeConstraints { make in
  282. make.centerY.equalToSuperview()
  283. make.trailing.equalTo(addButton.snp.leading).offset(-10)
  284. }
  285. minuButton.setTitle("-", for: .normal)
  286. minuButton.setTitleColor(.text_4, for: .normal)
  287. minuButton.setTitleColor(.text_2, for: .disabled)
  288. minuButton.backgroundColor = .primary_1
  289. minuButton.layer.cornerRadius = 12
  290. minuButton.addAction(UIAction(handler: { [weak self] _ in
  291. guard let self else { return }
  292. self.curCount -= 1
  293. }), for: .touchUpInside)
  294. countView.addSubview(minuButton)
  295. minuButton.snp.makeConstraints { make in
  296. make.centerY.equalToSuperview()
  297. make.trailing.equalTo(countLabel.snp.leading).offset(-10)
  298. make.width.height.equalTo(24)
  299. }
  300. let discountView = UIView()
  301. discountView.snp.makeConstraints { make in
  302. make.height.equalTo(38)
  303. }
  304. container.addArrangedSubview(discountView)
  305. let discountTitleLabel = UILabel()
  306. discountTitleLabel.font = .heading_h5
  307. discountTitleLabel.textColor = .text_5
  308. discountTitleLabel.text = .init(key: "A00129")
  309. discountView.addSubview(discountTitleLabel)
  310. discountTitleLabel.snp.makeConstraints { make in
  311. make.leading.equalToSuperview().offset(12)
  312. make.centerY.equalToSuperview()
  313. }
  314. let discountArrow = UIImageView.arrowImageView(size: 14)
  315. discountArrow.tintColor = .text_2
  316. discountView.addSubview(discountArrow)
  317. discountArrow.snp.makeConstraints { make in
  318. make.centerY.equalToSuperview()
  319. make.trailing.equalToSuperview().offset(-12)
  320. }
  321. let discountDescLabel = UILabel()
  322. discountDescLabel.font = .body_s
  323. discountDescLabel.text = .init(key: "A00130")
  324. discountDescLabel.textColor = .text_2
  325. discountView.addSubview(discountDescLabel)
  326. discountDescLabel.snp.makeConstraints { make in
  327. make.centerY.equalToSuperview()
  328. make.trailing.equalTo(discountArrow.snp.leading).offset(-3)
  329. }
  330. let emptyView = UIView()
  331. emptyView.snp.makeConstraints { make in
  332. make.height.equalTo(10)
  333. }
  334. container.addArrangedSubview(emptyView)
  335. return container
  336. }
  337. private func buildExtra() -> UIView {
  338. let container = UIView()
  339. container.backgroundColor = .fill
  340. container.layer.cornerRadius = 12
  341. container.snp.makeConstraints { make in
  342. make.height.equalTo(50)
  343. }
  344. let titleLabel = UILabel()
  345. titleLabel.font = .heading_h5
  346. titleLabel.textColor = .text_5
  347. titleLabel.text = .init(key: "A00086")
  348. titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  349. container.addSubview(titleLabel)
  350. titleLabel.snp.makeConstraints { make in
  351. make.centerY.equalToSuperview()
  352. make.leading.equalToSuperview().offset(12)
  353. }
  354. extraInput.font = .body_s
  355. extraInput.textColor = .text_5
  356. extraInput.placeholder = .init(key: "A00125", LNOrderManager.orderExtraMaxLength)
  357. extraInput.clearButtonMode = .whileEditing
  358. extraInput.delegate = self
  359. container.addSubview(extraInput)
  360. extraInput.snp.makeConstraints { make in
  361. make.centerY.equalToSuperview()
  362. make.leading.equalTo(titleLabel.snp.trailing).offset(8)
  363. make.trailing.equalToSuperview().offset(-12)
  364. }
  365. return container
  366. }
  367. private func buildMenu() -> UIView {
  368. let holder = UIView()
  369. let container = UIView()
  370. container.backgroundColor = .init(hex: "#ECF6FF")
  371. container.layer.cornerRadius = 23.5
  372. holder.addSubview(container)
  373. container.snp.makeConstraints { make in
  374. make.directionalHorizontalEdges.equalToSuperview().inset(16)
  375. make.top.equalToSuperview().offset(8)
  376. make.bottom.equalToSuperview().offset(-view.safeBottomInset - 10)
  377. }
  378. orderButton.setBackgroundImage(.init(named: "ic_skill_order_long"), for: .normal)
  379. orderButton.setTitle(.init(key: "A00041"), for: .normal)
  380. orderButton.setTitleColor(.text_1, for: .normal)
  381. orderButton.titleLabel?.font = .heading_h3
  382. orderButton.addAction(UIAction(handler: { [weak self] _ in
  383. guard let self else { return }
  384. let panel = LNCreateOrderPanel()
  385. if let skill {
  386. panel.update(skill, count: curCount, extra: extraInput.text ?? "")
  387. } else if let qrDetail {
  388. panel.update(qrDetail, count: curCount, extra: extraInput.text ?? "")
  389. } else { return }
  390. panel.completionHandler = { [weak self] orderId in
  391. guard let self else { return }
  392. if let skill {
  393. view.pushToChat(uid: skill.userNo)
  394. } else if let qrDetail {
  395. view.pushToChat(uid: qrDetail.sellerUserNo)
  396. }
  397. navigationController?.viewControllers.removeAll { $0 is LNCreateOrderViewController }
  398. }
  399. panel.showIn()
  400. }), for: .touchUpInside)
  401. container.addSubview(orderButton)
  402. orderButton.snp.makeConstraints { make in
  403. make.trailing.equalToSuperview()
  404. make.verticalEdges.equalToSuperview()
  405. }
  406. let descView = UIView()
  407. container.addSubview(descView)
  408. descView.snp.makeConstraints { make in
  409. make.leading.verticalEdges.equalToSuperview()
  410. make.trailing.equalTo(orderButton.snp.leading)
  411. }
  412. let priceView = UIView()
  413. descView.addSubview(priceView)
  414. priceView.snp.makeConstraints { make in
  415. make.center.equalToSuperview()
  416. }
  417. let coin = UIImageView.coinImageView()
  418. priceView.addSubview(coin)
  419. coin.snp.makeConstraints { make in
  420. make.leading.centerY.equalToSuperview()
  421. make.width.height.equalTo(16)
  422. }
  423. costLabel.textColor = .text_4
  424. priceView.addSubview(costLabel)
  425. costLabel.snp.makeConstraints { make in
  426. make.verticalEdges.trailing.equalToSuperview()
  427. make.leading.equalTo(coin.snp.trailing).offset(4)
  428. }
  429. return holder
  430. }
  431. }
  432. #if DEBUG
  433. import SwiftUI
  434. struct LNCreateOrderViewControllerPreview: UIViewControllerRepresentable {
  435. func makeUIViewController(context: Context) -> some UIViewController {
  436. LNCreateOrderViewController()
  437. }
  438. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
  439. }
  440. #Preview(body: {
  441. LNCreateOrderViewControllerPreview()
  442. })
  443. #endif