LNCreateOrderPanel.swift 20 KB

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