LNCreateOrderPanel.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. //
  2. // LNCreateOrderPanel.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/22.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. class LNCreateOrderPanel: LNPopupView {
  11. private let gameNameLabel = UILabel()
  12. private let avatar = UIImageView()
  13. private let nameLabel = UILabel()
  14. private let priceLabel = UILabel()
  15. private let countEditView = UIView()
  16. private let minuButton = UIButton()
  17. private let customCountLabel = UILabel()
  18. private let addButton = UIButton()
  19. private let countLabel = UILabel()
  20. private let costLabel = UILabel()
  21. private let discountView = UIView()
  22. private let newbieView = LNNewbieDiscountView()
  23. private let discountCoinLabel = UILabel()
  24. private let extraView = UIView()
  25. private let extraInput = UITextField()
  26. private let curCoinLabel = UILabel()
  27. private var skillId: String?
  28. private var qrCode: String?
  29. private var price: Double = 0.0
  30. private var curCount = 1 {
  31. didSet {
  32. countLabel.text = "x\(curCount)"
  33. customCountLabel.text = "\(curCount)"
  34. if let discount = LNOrderManager.shared.discountFor(price) {
  35. costLabel.text = (price * Double(curCount) - (1 - discount) * price).toDisplay
  36. } else {
  37. costLabel.text = (price * Double(curCount)).toDisplay
  38. }
  39. if curCount <= 1 {
  40. minuButton.isEnabled = false
  41. } else {
  42. minuButton.isEnabled = true
  43. }
  44. }
  45. }
  46. var editable: Bool = false {
  47. didSet {
  48. extraView.isHidden = !editable
  49. countEditView.isHidden = !editable
  50. countLabel.isHidden = editable
  51. }
  52. }
  53. private var targetUid: String?
  54. var completionHandler: ((String) -> Void)?
  55. override init(frame: CGRect) {
  56. super.init(frame: frame)
  57. setupViews()
  58. LNEventDeliver.addObserver(self)
  59. }
  60. func update(_ detail: LNGameMateSkillDetailVO, count: Int, extra: String) {
  61. gameNameLabel.text = detail.categoryName
  62. avatar.showAvatar(detail.avatar)
  63. nameLabel.text = detail.nickname
  64. priceLabel.text = "\(detail.price.toDisplay)/\(detail.unit)"
  65. if let discount = LNOrderManager.shared.discountFor(detail.price) {
  66. discountView.isHidden = false
  67. newbieView.update(1 - discount)
  68. discountCoinLabel.text = "-\(detail.price * (1 - discount))"
  69. } else {
  70. discountView.isHidden = true
  71. }
  72. skillId = detail.id
  73. price = detail.price
  74. targetUid = detail.userNo
  75. curCount = count
  76. extraInput.text = extra
  77. }
  78. func update(_ skill: LNGameMateSkillVO, user: LNUserProfileVO) {
  79. gameNameLabel.text = skill.name
  80. avatar.showAvatar(user.avatar)
  81. nameLabel.text = user.nickname
  82. priceLabel.text = "\(skill.price.toDisplay)/\(skill.unit)"
  83. if let discount = LNOrderManager.shared.discountFor(skill.price) {
  84. discountView.isHidden = false
  85. newbieView.update(1 - discount)
  86. discountCoinLabel.text = "-\(skill.price * (1 - discount))"
  87. } else {
  88. discountView.isHidden = true
  89. }
  90. skillId = skill.id
  91. price = skill.price
  92. targetUid = user.userNo
  93. curCount = 1
  94. }
  95. func update(_ detail: LNQRCodeDetailResponse, count: Int, extra: String) {
  96. gameNameLabel.text = detail.bizCategoryName
  97. avatar.showAvatar(detail.avatar)
  98. nameLabel.text = detail.nickname
  99. priceLabel.text = "\(detail.price.toDisplay)/\(detail.unit)"
  100. if let discount = LNOrderManager.shared.discountFor(detail.price) {
  101. discountView.isHidden = false
  102. newbieView.update(1 - discount)
  103. discountCoinLabel.text = "-\(detail.price * (1 - discount))"
  104. } else {
  105. discountView.isHidden = true
  106. }
  107. qrCode = detail.qrCode
  108. price = detail.price
  109. targetUid = detail.sellerUserNo
  110. curCount = count
  111. extraInput.text = extra
  112. }
  113. required init?(coder: NSCoder) {
  114. fatalError("init(coder:) has not been implemented")
  115. }
  116. }
  117. extension LNCreateOrderPanel: LNPurchaseManagerNotify {
  118. func onUserWalletInfoChanged(info: LNUserWalletInfo) {
  119. curCoinLabel.text = info.coin.toDisplay
  120. }
  121. }
  122. extension LNCreateOrderPanel: UITextFieldDelegate {
  123. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  124. let currentText = textField.text ?? ""
  125. guard let range = Range(range, in: currentText) else { return false }
  126. let newText = currentText.replacingCharacters(in: range, with: string)
  127. if newText.count < currentText.count {
  128. return true
  129. }
  130. return newText.count <= LNOrderManager.orderExtraMaxLength
  131. }
  132. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  133. textField.resignFirstResponder()
  134. return true
  135. }
  136. }
  137. extension LNCreateOrderPanel {
  138. private func setupViews() {
  139. container.backgroundColor = .primary_1
  140. let stackView = UIStackView()
  141. stackView.axis = .vertical
  142. stackView.spacing = 10
  143. container.addSubview(stackView)
  144. stackView.snp.makeConstraints { make in
  145. make.horizontalEdges.equalToSuperview().inset(12)
  146. make.top.equalToSuperview().offset(20)
  147. }
  148. let billInfo = buildBillInfo()
  149. stackView.addArrangedSubview(billInfo)
  150. let extra = buildExtra()
  151. stackView.addArrangedSubview(extra)
  152. let wallet = buildWalletInfo()
  153. stackView.addArrangedSubview(wallet)
  154. let confirmButton = UIButton()
  155. confirmButton.setTitle(.init(key: "A00123"), for: .normal)
  156. confirmButton.setTitleColor(.text_1, for: .normal)
  157. confirmButton.titleLabel?.font = .heading_h3
  158. confirmButton.setBackgroundImage(.primary_8, for: .normal)
  159. confirmButton.layer.cornerRadius = 23.5
  160. confirmButton.clipsToBounds = true
  161. confirmButton.addAction(UIAction(handler: { [weak self] _ in
  162. guard let self else { return }
  163. let extra = extraInput.text ?? ""
  164. let handler = { [weak self] (orderNo: String?) in
  165. guard let self else { return }
  166. guard let orderNo else { return }
  167. dismiss()
  168. if let targetUid,
  169. !(navigationController?.topViewController is LNIMChatViewController) {
  170. pushToChat(uid: targetUid)
  171. }
  172. completionHandler?(orderNo)
  173. }
  174. if let skillId {
  175. LNOrderManager.shared.createOrder(
  176. skillId: skillId, count: curCount, remark: extra, handler: handler)
  177. } else if let qrCode {
  178. LNOrderManager.shared.createQRCodeOrder(
  179. data: qrCode, count: curCount, extra: extra, handler: handler)
  180. }
  181. }), for: .touchUpInside)
  182. container.addSubview(confirmButton)
  183. confirmButton.snp.makeConstraints { make in
  184. make.horizontalEdges.equalToSuperview().inset(12)
  185. make.top.equalTo(stackView.snp.bottom).offset(15)
  186. make.bottom.equalToSuperview().offset(commonBottomInset)
  187. make.height.equalTo(47)
  188. }
  189. }
  190. private func buildBillInfo() -> UIView {
  191. let container = UIView()
  192. container.backgroundColor = .fill
  193. container.layer.cornerRadius = 12
  194. container.onTap { [weak self] in
  195. guard let self else { return }
  196. endEditing(true)
  197. }
  198. let stackView = UIStackView()
  199. stackView.axis = .vertical
  200. container.addSubview(stackView)
  201. stackView.snp.makeConstraints { make in
  202. make.edges.equalToSuperview()
  203. }
  204. stackView.addArrangedSubview(buildUserInfo())
  205. stackView.addArrangedSubview(buildPrice())
  206. stackView.addArrangedSubview(buildDiscount())
  207. stackView.addArrangedSubview(buildCost())
  208. return container
  209. }
  210. private func buildUserInfo() -> UIView {
  211. let container = UIView()
  212. container.isUserInteractionEnabled = false
  213. container.snp.makeConstraints { make in
  214. make.height.equalTo(83)
  215. }
  216. let skillTag = UIImageView()
  217. skillTag.image = .icOrderSkillBg
  218. container.addSubview(skillTag)
  219. skillTag.snp.makeConstraints { make in
  220. make.leading.top.equalToSuperview()
  221. }
  222. avatar.layer.cornerRadius = 30
  223. avatar.layer.borderWidth = 2
  224. avatar.layer.borderColor = UIColor.fill.cgColor
  225. avatar.backgroundColor = .fill
  226. avatar.clipsToBounds = true
  227. container.addSubview(avatar)
  228. avatar.snp.makeConstraints { make in
  229. make.centerX.equalToSuperview()
  230. make.top.equalToSuperview().offset(-8)
  231. make.width.height.equalTo(60)
  232. }
  233. gameNameLabel.font = .heading_h5
  234. gameNameLabel.textColor = .text_1
  235. gameNameLabel.textAlignment = .center
  236. skillTag.addSubview(gameNameLabel)
  237. gameNameLabel.snp.makeConstraints { make in
  238. make.centerY.equalToSuperview()
  239. make.horizontalEdges.equalToSuperview().inset(14)
  240. make.width.lessThanOrEqualTo(82)
  241. }
  242. nameLabel.font = .heading_h4
  243. nameLabel.textColor = .text_5
  244. container.addSubview(nameLabel)
  245. nameLabel.snp.makeConstraints { make in
  246. make.centerX.equalToSuperview()
  247. make.top.equalTo(avatar.snp.bottom).offset(3)
  248. }
  249. let line = UIView()
  250. line.backgroundColor = .fill_2
  251. container.addSubview(line)
  252. line.snp.makeConstraints { make in
  253. make.horizontalEdges.equalToSuperview().inset(12)
  254. make.height.equalTo(1)
  255. make.bottom.equalToSuperview()
  256. }
  257. return container
  258. }
  259. private func buildPrice() -> UIView {
  260. let container = UIView()
  261. container.snp.makeConstraints { make in
  262. make.height.equalTo(49)
  263. }
  264. let titleLabel = UILabel()
  265. titleLabel.font = .heading_h5
  266. titleLabel.textColor = .text_5
  267. titleLabel.text = .init(key: "A00128")
  268. container.addSubview(titleLabel)
  269. titleLabel.snp.makeConstraints { make in
  270. make.leading.equalToSuperview().offset(12)
  271. make.centerY.equalToSuperview()
  272. }
  273. let coin = UIImageView.coinImageView()
  274. container.addSubview(coin)
  275. coin.snp.makeConstraints { make in
  276. make.centerY.equalToSuperview()
  277. make.leading.equalTo(titleLabel.snp.trailing).offset(6)
  278. make.width.height.equalTo(18)
  279. }
  280. priceLabel.font = .body_m
  281. priceLabel.textColor = .text_5
  282. container.addSubview(priceLabel)
  283. priceLabel.snp.makeConstraints { make in
  284. make.leading.equalTo(coin.snp.trailing).offset(2)
  285. make.centerY.equalToSuperview()
  286. }
  287. countLabel.font = .body_m
  288. countLabel.textColor = .text_5
  289. container.addSubview(countLabel)
  290. countLabel.snp.makeConstraints { make in
  291. make.centerY.equalToSuperview()
  292. make.trailing.equalToSuperview().offset(-16)
  293. }
  294. countEditView.isHidden = true
  295. container.addSubview(countEditView)
  296. countEditView.snp.makeConstraints { make in
  297. make.verticalEdges.equalToSuperview()
  298. make.trailing.equalToSuperview()
  299. }
  300. addButton.setTitle("+", for: .normal)
  301. addButton.setTitleColor(.text_4, for: .normal)
  302. addButton.setTitleColor(.text_2, for: .disabled)
  303. addButton.backgroundColor = .primary_1
  304. addButton.layer.cornerRadius = 12
  305. addButton.addAction(UIAction(handler: { [weak self] _ in
  306. guard let self else { return }
  307. curCount += 1
  308. }), for: .touchUpInside)
  309. countEditView.addSubview(addButton)
  310. addButton.snp.makeConstraints { make in
  311. make.centerY.equalToSuperview()
  312. make.trailing.equalToSuperview().offset(-12)
  313. make.width.height.equalTo(24)
  314. }
  315. customCountLabel.font = .body_m
  316. customCountLabel.textColor = .text_5
  317. countEditView.addSubview(customCountLabel)
  318. customCountLabel.snp.makeConstraints { make in
  319. make.centerY.equalToSuperview()
  320. make.trailing.equalTo(addButton.snp.leading).offset(-10)
  321. }
  322. minuButton.setTitle("-", for: .normal)
  323. minuButton.setTitleColor(.text_4, for: .normal)
  324. minuButton.setTitleColor(.text_2, for: .disabled)
  325. minuButton.backgroundColor = .primary_1
  326. minuButton.layer.cornerRadius = 12
  327. minuButton.addAction(UIAction(handler: { [weak self] _ in
  328. guard let self else { return }
  329. self.curCount -= 1
  330. }), for: .touchUpInside)
  331. countEditView.addSubview(minuButton)
  332. minuButton.snp.makeConstraints { make in
  333. make.centerY.equalToSuperview()
  334. make.trailing.equalTo(customCountLabel.snp.leading).offset(-10)
  335. make.width.height.equalTo(24)
  336. make.leading.equalToSuperview()
  337. }
  338. return container
  339. }
  340. private func buildDiscount() -> UIView {
  341. discountView.snp.makeConstraints { make in
  342. make.height.equalTo(49)
  343. }
  344. let titleLabel = UILabel()
  345. titleLabel.font = .heading_h5
  346. titleLabel.textColor = .text_5
  347. titleLabel.text = .init(key: "A00129")
  348. discountView.addSubview(titleLabel)
  349. titleLabel.snp.makeConstraints { make in
  350. make.leading.equalToSuperview().offset(12)
  351. make.centerY.equalToSuperview()
  352. }
  353. discountView.addSubview(newbieView)
  354. newbieView.snp.makeConstraints { make in
  355. make.centerY.equalToSuperview()
  356. make.leading.equalTo(titleLabel.snp.trailing).offset(8)
  357. }
  358. discountCoinLabel.font = .heading_h3
  359. discountCoinLabel.textColor = .init(hex: "#FF6F32")
  360. discountView.addSubview(discountCoinLabel)
  361. discountCoinLabel.snp.makeConstraints { make in
  362. make.centerY.equalToSuperview()
  363. make.trailing.equalToSuperview().offset(-12)
  364. }
  365. let coin = UIImageView.coinImageView()
  366. discountView.addSubview(coin)
  367. coin.snp.makeConstraints { make in
  368. make.centerY.equalToSuperview()
  369. make.trailing.equalTo(discountCoinLabel.snp.leading).offset(-2)
  370. make.width.height.equalTo(20)
  371. }
  372. return discountView
  373. }
  374. private func buildCost() -> UIView {
  375. let container = UIView()
  376. container.isUserInteractionEnabled = false
  377. container.snp.makeConstraints { make in
  378. make.height.equalTo(49)
  379. }
  380. costLabel.font = .heading_h1
  381. costLabel.textColor = .text_5
  382. container.addSubview(costLabel)
  383. costLabel.snp.makeConstraints { make in
  384. make.centerY.equalToSuperview()
  385. make.trailing.equalToSuperview().offset(-12)
  386. }
  387. let coin = UIImageView.coinImageView()
  388. container.addSubview(coin)
  389. coin.snp.makeConstraints { make in
  390. make.centerY.equalToSuperview()
  391. make.trailing.equalTo(costLabel.snp.leading).offset(-2)
  392. make.width.height.equalTo(24)
  393. }
  394. let totalLabel = UILabel()
  395. totalLabel.text = .init(key: "A00124")
  396. totalLabel.font = .body_m
  397. totalLabel.textColor = .text_5
  398. container.addSubview(totalLabel)
  399. totalLabel.snp.makeConstraints { make in
  400. make.centerY.equalToSuperview()
  401. make.trailing.equalTo(coin.snp.leading).offset(-2)
  402. }
  403. return container
  404. }
  405. private func buildExtra() -> UIView {
  406. extraView.isHidden = true
  407. extraView.backgroundColor = .fill
  408. extraView.layer.cornerRadius = 12
  409. extraView.snp.makeConstraints { make in
  410. make.height.equalTo(50)
  411. }
  412. let titleLabel = UILabel()
  413. titleLabel.font = .heading_h5
  414. titleLabel.text = .init(key: "A00086")
  415. titleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  416. titleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
  417. titleLabel.textColor = .text_5
  418. extraView.addSubview(titleLabel)
  419. titleLabel.snp.makeConstraints { make in
  420. make.leading.equalToSuperview().offset(12)
  421. make.centerY.equalToSuperview()
  422. }
  423. extraInput.placeholder = .init(key: "A00125", LNOrderManager.orderExtraMaxLength)
  424. extraInput.font = .body_s
  425. extraInput.textColor = .text_5
  426. extraInput.clearButtonMode = .whileEditing
  427. extraInput.delegate = self
  428. extraInput.visibleView = extraView
  429. extraView.addSubview(extraInput)
  430. extraInput.snp.makeConstraints { make in
  431. make.centerY.equalToSuperview()
  432. make.leading.equalTo(titleLabel.snp.trailing).offset(8)
  433. make.trailing.equalToSuperview().offset(-12)
  434. }
  435. return extraView
  436. }
  437. private func buildWalletInfo() -> UIView {
  438. let container = UIView()
  439. container.isUserInteractionEnabled = false
  440. container.backgroundColor = .fill
  441. container.layer.cornerRadius = 12
  442. container.snp.makeConstraints { make in
  443. make.height.equalTo(66)
  444. }
  445. let icon = UIImageView()
  446. icon.image = .icWalletWithBg
  447. container.addSubview(icon)
  448. icon.snp.makeConstraints { make in
  449. make.centerY.equalToSuperview()
  450. make.leading.equalToSuperview().offset(12)
  451. }
  452. let infoView = UIView()
  453. container.addSubview(infoView)
  454. infoView.snp.makeConstraints { make in
  455. make.centerY.equalToSuperview()
  456. make.leading.equalTo(icon.snp.trailing).offset(6)
  457. }
  458. let titleLabel = UILabel()
  459. titleLabel.text = .init(key: "A00126")
  460. titleLabel.font = .heading_h5
  461. titleLabel.textColor = .text_5
  462. infoView.addSubview(titleLabel)
  463. titleLabel.snp.makeConstraints { make in
  464. make.leading.top.equalToSuperview()
  465. make.trailing.equalToSuperview()
  466. }
  467. curCoinLabel.text = myWalletInfo.coin.toDisplay
  468. curCoinLabel.font = .body_m
  469. curCoinLabel.textColor = .text_3
  470. infoView.addSubview(curCoinLabel)
  471. curCoinLabel.snp.makeConstraints { make in
  472. make.trailing.bottom.equalToSuperview()
  473. make.top.equalTo(titleLabel.snp.bottom).offset(2)
  474. }
  475. let coin = UIImageView.coinImageView()
  476. infoView.addSubview(coin)
  477. coin.snp.makeConstraints { make in
  478. make.centerY.equalTo(curCoinLabel)
  479. make.leading.equalToSuperview()
  480. make.trailing.equalTo(curCoinLabel.snp.leading).offset(-2)
  481. make.width.height.equalTo(16)
  482. }
  483. let check = UIImageView()
  484. check.image = .icCheck
  485. container.addSubview(check)
  486. check.snp.makeConstraints { make in
  487. make.centerY.equalToSuperview()
  488. make.trailing.equalToSuperview().offset(-12)
  489. make.width.height.equalTo(22)
  490. }
  491. return container
  492. }
  493. }
  494. #if DEBUG
  495. import SwiftUI
  496. struct LNCreateOrderPanelPreview: UIViewRepresentable {
  497. func makeUIView(context: Context) -> some UIView {
  498. let container = UIView()
  499. container.backgroundColor = .lightGray
  500. let view = LNCreateOrderPanel()
  501. view.popup(container)
  502. return container
  503. }
  504. func updateUIView(_ uiView: UIViewType, context: Context) { }
  505. }
  506. #Preview(body: {
  507. LNCreateOrderPanelPreview()
  508. })
  509. #endif // DEBUG