LNOrderDetailViewController.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. //
  2. // LNOrderDetailViewController.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/19.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. extension UIView {
  11. func pushToOrderDetail(orderId: String) {
  12. let vc = LNOrderDetailViewController(orderId: orderId)
  13. navigationController?.pushViewController(vc, animated: true)
  14. }
  15. }
  16. class LNOrderDetailViewController: LNViewController {
  17. private let orderId: String
  18. private let backgroundIc = UIImageView()
  19. private let starView = LNFiveStarScoreView()
  20. private let stateIc = UIImageView()
  21. private let stateLabel = UILabel()
  22. private var timer: Timer?
  23. private let countDownLabel = UILabel()
  24. private let detailView = LNOrderDetailCardView()
  25. private let cancelButton = UIButton()
  26. private let refundButton = UIButton()
  27. private let commentButton = UIButton()
  28. private let completeView = UIView()
  29. private let refundView = LNOrderRefundInfoView()
  30. private var curDetail: LNOrderDetailResponse?
  31. init(orderId: String) {
  32. self.orderId = orderId
  33. super.init(nibName: nil, bundle: nil)
  34. }
  35. override func viewDidLoad() {
  36. super.viewDidLoad()
  37. setupViews()
  38. LNEventDeliver.addObserver(self)
  39. reloadDetail()
  40. }
  41. required init?(coder: NSCoder) {
  42. fatalError("init(coder:) has not been implemented")
  43. }
  44. }
  45. extension LNOrderDetailViewController: LNOrderManagerNotify {
  46. func onOrderInfoChanged(orderId: String) {
  47. guard orderId == self.orderId else { return }
  48. reloadDetail()
  49. }
  50. }
  51. extension LNOrderDetailViewController {
  52. private func reloadDetail() {
  53. LNOrderManager.shared.getOrderDetail(orderId: orderId) { [weak self] detail in
  54. guard let self else { return }
  55. guard let detail else { return }
  56. curDetail = detail
  57. update(detail)
  58. }
  59. }
  60. }
  61. extension LNOrderDetailViewController {
  62. private func startCountDown() {
  63. stopCountDown()
  64. updateRemain()
  65. let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
  66. guard let self else { return }
  67. if updateRemain() {
  68. stopCountDown()
  69. reloadDetail()
  70. }
  71. }
  72. RunLoop.current.add(timer, forMode: .common)
  73. self.timer = timer
  74. }
  75. private func stopCountDown() {
  76. timer?.invalidate()
  77. timer = nil
  78. }
  79. @discardableResult
  80. private func updateRemain() -> Bool {
  81. guard let curDetail else { return true }
  82. let remain = 3600 - (Int(curTime) - curDetail.orderInfo.createTime / 1_000)
  83. let countDownText = remain.timeCountDisplay
  84. let text: String = .init(key: "A00134", countDownText)
  85. let attr = NSMutableAttributedString(string: text)
  86. let range = (text as NSString).range(of: countDownText)
  87. attr.addAttributes([.font: UIFont.heading_h4,
  88. .foregroundColor: UIColor.primary_5], range: range)
  89. countDownLabel.attributedText = attr
  90. return remain <= 0
  91. }
  92. }
  93. extension LNOrderDetailViewController {
  94. private func update(_ item: LNOrderDetailResponse) {
  95. starView.isHidden = true
  96. countDownLabel.isHidden = true
  97. cancelButton.isHidden = true
  98. commentButton.isHidden = true
  99. completeView.isHidden = true
  100. refundButton.isHidden = true
  101. stopCountDown()
  102. detailView.update(item)
  103. refundView.update(item)
  104. if item.orderInfo.refundApply {
  105. backgroundIc.image = .icOrderCancelledBg
  106. stateIc.image = .icOrderStatusInProgress
  107. stateLabel.text = .init(key: "A00135")
  108. return
  109. }
  110. switch item.orderInfo.status {
  111. case .created:
  112. break
  113. case .waitingForAccept:
  114. backgroundIc.image = .icOrderNormalBg
  115. stateIc.image = .icOrderStatusInProgress
  116. stateLabel.text = .init(key: "A00136")
  117. countDownLabel.isHidden = false
  118. cancelButton.isHidden = false
  119. startCountDown()
  120. case .completed:
  121. backgroundIc.image = .icOrderNormalBg
  122. stateIc.image = .icOrderStatusComplete
  123. stateLabel.text = .init(key: "A00137")
  124. starView.isHidden = item.orderInfo.star == 0
  125. starView.score = item.orderInfo.star
  126. commentButton.isHidden = item.orderInfo.star != 0
  127. case .refunded:
  128. backgroundIc.image = .icOrderCancelledBg
  129. stateIc.image = .icOrderStatusRefund
  130. stateLabel.text = .init(key: "A00138")
  131. case .accepted:
  132. backgroundIc.image = .icOrderNormalBg
  133. stateIc.image = .icOrderStatusWaiting
  134. stateLabel.text = .init(key: "A00139")
  135. refundButton.isHidden = false
  136. case .rejected:
  137. backgroundIc.image = .icOrderCancelledBg
  138. stateIc.image = .icOrderStatusCancel
  139. stateLabel.text = .init(key: "A00140")
  140. case .servicing:
  141. backgroundIc.image = .icOrderNormalBg
  142. stateIc.image = .icOrderStatusInProgress
  143. stateLabel.text = .init(key: "A00141")
  144. refundButton.isHidden = false
  145. case .serviceDone:
  146. backgroundIc.image = .icOrderNormalBg
  147. stateIc.image = .icOrderStatusInProgress
  148. stateLabel.text = .init(key: "A00141")
  149. completeView.isHidden = false
  150. case .cancelled:
  151. backgroundIc.image = .icOrderCancelledBg
  152. stateIc.image = .icOrderStatusCancel
  153. stateLabel.text = .init(key: "A00142")
  154. }
  155. }
  156. private func setupViews() {
  157. view.backgroundColor = .primary_1
  158. title = .init(key: "A00143")
  159. view.addSubview(backgroundIc)
  160. backgroundIc.snp.makeConstraints { make in
  161. make.horizontalEdges.equalToSuperview()
  162. make.top.equalToSuperview()
  163. }
  164. let bottomMenu = buildBottomMenu()
  165. view.addSubview(bottomMenu)
  166. bottomMenu.snp.makeConstraints { make in
  167. make.horizontalEdges.equalToSuperview().inset(16)
  168. make.bottom.equalToSuperview().offset(-view.safeBottomInset - 10)
  169. make.height.equalTo(0).priority(.low)
  170. }
  171. let scrollView = UIScrollView()
  172. scrollView.showsVerticalScrollIndicator = false
  173. scrollView.showsHorizontalScrollIndicator = false
  174. view.addSubview(scrollView)
  175. scrollView.snp.makeConstraints { make in
  176. make.horizontalEdges.equalToSuperview()
  177. make.top.equalToSuperview()
  178. make.bottom.equalTo(bottomMenu.snp.top)
  179. }
  180. let fakeView = UIView()
  181. scrollView.addSubview(fakeView)
  182. fakeView.snp.makeConstraints { make in
  183. make.horizontalEdges.equalToSuperview()
  184. make.top.equalToSuperview()
  185. make.width.equalToSuperview()
  186. make.height.equalTo(0)
  187. }
  188. let statusView = buildStatusView()
  189. scrollView.addSubview(statusView)
  190. statusView.snp.makeConstraints { make in
  191. make.horizontalEdges.equalToSuperview().inset(24)
  192. make.top.equalToSuperview().offset(44)
  193. }
  194. let stackView = UIStackView()
  195. stackView.axis = .vertical
  196. stackView.spacing = 10
  197. scrollView.addSubview(stackView)
  198. stackView.snp.makeConstraints { make in
  199. make.horizontalEdges.equalToSuperview().inset(16)
  200. make.bottom.equalToSuperview()
  201. make.top.equalTo(statusView.snp.bottom).offset(30)
  202. }
  203. stackView.addArrangedSubview(detailView)
  204. refundView.isHidden = true
  205. stackView.addArrangedSubview(refundView)
  206. }
  207. private func buildStatusView() -> UIView {
  208. let stackView = UIStackView()
  209. stackView.axis = .vertical
  210. stackView.spacing = 7
  211. stackView.alignment = .center
  212. starView.icSize = 30
  213. starView.spacing = 14
  214. starView.startType = .whiteBorder
  215. stackView.addArrangedSubview(starView)
  216. let stateView = UIView()
  217. stateView.addSubview(stateIc)
  218. stateIc.snp.makeConstraints { make in
  219. make.centerY.equalToSuperview()
  220. make.leading.equalToSuperview()
  221. make.width.height.equalTo(24)
  222. }
  223. stateLabel.font = .heading_h1
  224. stateLabel.textColor = .text_5
  225. stateLabel.numberOfLines = 0
  226. stateView.addSubview(stateLabel)
  227. stateLabel.snp.makeConstraints { make in
  228. make.verticalEdges.equalToSuperview()
  229. make.trailing.equalToSuperview()
  230. make.leading.equalTo(stateIc.snp.trailing).offset(8)
  231. }
  232. stackView.addArrangedSubview(stateView)
  233. countDownLabel.font = .body_s
  234. countDownLabel.textColor = .text_5
  235. stackView.addArrangedSubview(countDownLabel)
  236. return stackView
  237. }
  238. private func buildBottomMenu() -> UIView {
  239. let stackView = UIStackView()
  240. stackView.axis = .vertical
  241. stackView.spacing = 0
  242. stackView.snp.makeConstraints { make in
  243. make.height.equalTo(0).priority(.low)
  244. }
  245. cancelButton.isHidden = true
  246. cancelButton.setTitle(.init(key: "A00003"), for: .normal)
  247. cancelButton.setTitleColor(.text_3, for: .normal)
  248. cancelButton.titleLabel?.font = .heading_h3
  249. cancelButton.layer.cornerRadius = 23.5
  250. cancelButton.layer.borderWidth = 1
  251. cancelButton.layer.borderColor = UIColor.text_2.cgColor
  252. cancelButton.addAction(UIAction(handler: { [weak self] _ in
  253. guard let self else { return }
  254. LNCommonAlertView.showCancelOrderAlert(orderId: orderId)
  255. }), for: .touchUpInside)
  256. stackView.addArrangedSubview(cancelButton)
  257. cancelButton.snp.makeConstraints { make in
  258. make.height.equalTo(47)
  259. }
  260. refundButton.isHidden = true
  261. refundButton.setTitle(.init(key: "A00144"), for: .normal)
  262. refundButton.setTitleColor(.text_3, for: .normal)
  263. refundButton.titleLabel?.font = .heading_h3
  264. refundButton.layer.cornerRadius = 23.5
  265. refundButton.layer.borderWidth = 1
  266. refundButton.layer.borderColor = UIColor.text_2.cgColor
  267. refundButton.addAction(UIAction(handler: { [weak self] _ in
  268. guard let self else { return }
  269. view.pushToOrderRefund(orderId)
  270. }), for: .touchUpInside)
  271. stackView.addArrangedSubview(refundButton)
  272. refundButton.snp.makeConstraints { make in
  273. make.height.equalTo(47)
  274. }
  275. let cover = UIView()
  276. cover.backgroundColor = .fill
  277. cover.layer.cornerRadius = 23
  278. cover.isUserInteractionEnabled = false
  279. commentButton.addSubview(cover)
  280. cover.snp.makeConstraints { make in
  281. make.edges.equalToSuperview().inset(1)
  282. }
  283. commentButton.isHidden = true
  284. commentButton.setBackgroundImage(.primary_7, for: .normal)
  285. commentButton.setTitle(.init(key: "A00145"), for: .normal)
  286. commentButton.setTitleColor(.text_6, for: .normal)
  287. commentButton.titleLabel?.font = .heading_h3
  288. commentButton.layer.cornerRadius = 23.5
  289. commentButton.clipsToBounds = true
  290. commentButton.addAction(UIAction(handler: { [weak self] _ in
  291. guard let self else { return }
  292. guard let curDetail else { return }
  293. let panel = LNOrderCommentPanel()
  294. panel.update(curDetail.orderInfo.avatar, orderId: orderId)
  295. panel.popup()
  296. }), for: .touchUpInside)
  297. stackView.addArrangedSubview(commentButton)
  298. commentButton.snp.makeConstraints { make in
  299. make.height.equalTo(47)
  300. }
  301. completeView.isHidden = true
  302. stackView.addArrangedSubview(completeView)
  303. completeView.snp.makeConstraints { make in
  304. make.height.equalTo(47)
  305. }
  306. let refundButton = UIButton()
  307. refundButton.layer.cornerRadius = 23.5
  308. refundButton.setTitle(.init(key: "A00144"), for: .normal)
  309. refundButton.setTitleColor(.text_3, for: .normal)
  310. refundButton.titleLabel?.font = .heading_h3
  311. refundButton.layer.borderColor = UIColor.text_2.cgColor
  312. refundButton.layer.borderWidth = 1
  313. refundButton.addAction(UIAction(handler: { [weak self] _ in
  314. guard let self else { return }
  315. view.pushToOrderRefund(orderId)
  316. }), for: .touchUpInside)
  317. completeView.addSubview(refundButton)
  318. refundButton.snp.makeConstraints { make in
  319. make.leading.equalToSuperview()
  320. make.verticalEdges.equalToSuperview()
  321. make.width.equalToSuperview().multipliedBy(1.0/3.0)
  322. }
  323. let completeButton = UIButton()
  324. completeButton.layer.cornerRadius = 23.5
  325. completeButton.setTitle(.init(key: "A00025"), for: .normal)
  326. completeButton.setTitleColor(.text_1, for: .normal)
  327. completeButton.titleLabel?.font = .heading_h3
  328. completeButton.layer.cornerRadius = 23.5
  329. completeButton.setBackgroundImage(.primary_8, for: .normal)
  330. completeButton.clipsToBounds = true
  331. completeButton.addAction(UIAction(handler: { [weak self] _ in
  332. guard let self else { return }
  333. LNCommonAlertView.showFinishOrderAlert(orderId: orderId, completion: nil)
  334. }), for: .touchUpInside)
  335. completeView.addSubview(completeButton)
  336. completeButton.snp.makeConstraints { make in
  337. make.verticalEdges.equalToSuperview()
  338. make.leading.equalTo(refundButton.snp.trailing).offset(10)
  339. make.trailing.equalToSuperview()
  340. }
  341. return stackView
  342. }
  343. }
  344. #if DEBUG
  345. import SwiftUI
  346. struct LNOrderDetailViewControllerPreview: UIViewControllerRepresentable {
  347. func makeUIViewController(context: Context) -> some UIViewController {
  348. LNOrderDetailViewController(orderId: "")
  349. }
  350. func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { }
  351. }
  352. #Preview(body: {
  353. LNOrderDetailViewControllerPreview()
  354. })
  355. #endif