LNIMChatGameMateOrderView.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. //
  2. // LNIMChatGameMateOrderView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/12/31.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import Combine
  11. class LNIMChatGameMateOrderView: UIView {
  12. private let stackView = UIStackView()
  13. private let gameIc = UIImageView()
  14. private let titleLabel = UILabel()
  15. private let titleArrow = UIImageView.arrowImageView(size: 12)
  16. private let descLabel = UILabel()
  17. private let contentArrow = UIImageView.arrowImageView(size: 12)
  18. private let replyView = UIView()
  19. private let replyRemainLabel = UILabel()
  20. private var countDownTimer: Timer?
  21. private let goCallView = UIView()
  22. private let actionView = UIView()
  23. private let extraView = UIView()
  24. private let extraLabel = UILabel()
  25. private var curOrder: LNUnfinishedOrderVO?
  26. weak var viewModel: LNIMChatViewModel? {
  27. didSet {
  28. viewModel?.$myOrders.sink { [weak self] list in
  29. guard let self else { return }
  30. isHidden = list.isEmpty == true
  31. if let first = list.first {
  32. update(first)
  33. }
  34. }.store(in: &cancellables)
  35. }
  36. }
  37. override init(frame: CGRect) {
  38. super.init(frame: frame)
  39. setupViews()
  40. }
  41. required init?(coder: NSCoder) {
  42. fatalError("init(coder:) has not been implemented")
  43. }
  44. }
  45. extension LNIMChatGameMateOrderView {
  46. private func startCountDown() {
  47. stopCountDown()
  48. updateRemain()
  49. let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
  50. guard let self else { return }
  51. if updateRemain() {
  52. stopCountDown()
  53. viewModel?.getUnfinishOrder()
  54. }
  55. }
  56. RunLoop.current.add(timer, forMode: .common)
  57. countDownTimer = timer
  58. }
  59. private func stopCountDown() {
  60. countDownTimer?.invalidate()
  61. countDownTimer = nil
  62. }
  63. @discardableResult
  64. private func updateRemain() -> Bool {
  65. guard let curOrder else { return true }
  66. let remain = 3600 - (Int(curTime) - curOrder.createTime / 1_000)
  67. replyRemainLabel.text = remain.timeCountDisplay
  68. return remain <= 0
  69. }
  70. }
  71. extension LNIMChatGameMateOrderView {
  72. private func update(_ order: LNUnfinishedOrderVO) {
  73. curOrder = order
  74. let isCreator = order.buyerUserNo.isMyUid
  75. gameIc.sd_setImage(with: URL(string: order.categoryIcon))
  76. descLabel.text = "\(order.bizCategoryName)/\(order.unit) x \(order.purchaseQty)"
  77. extraLabel.text = .init(key: "A00056", order.customerRemark)
  78. actionView.isHidden = true
  79. extraView.isHidden = true
  80. replyView.isHidden = true
  81. goCallView.isHidden = true
  82. titleArrow.isHidden = true
  83. contentArrow.isHidden = true
  84. stopCountDown()
  85. switch order.status {
  86. case .created, .waitingForAccept:
  87. titleLabel.text = .init(key: "A00078")
  88. titleArrow.isHidden = false
  89. replyView.isHidden = false
  90. let remain = 3600 - (Int(curTime) - order.createTime / 1_000)
  91. replyRemainLabel.text = remain.timeCountDisplay
  92. startCountDown()
  93. if !isCreator {
  94. actionView.isHidden = false
  95. }
  96. case .accepted:
  97. titleLabel.text = .init(key: "A00079")
  98. contentArrow.isHidden = false
  99. if !isCreator, !order.customerRemark.isEmpty {
  100. extraView.isHidden = false
  101. }
  102. case .servicing:
  103. titleLabel.text = .init(key: "A00080")
  104. titleArrow.isHidden = false
  105. goCallView.isHidden = false
  106. if !isCreator, !order.customerRemark.isEmpty {
  107. extraView.isHidden = false
  108. }
  109. case .completed, .refunded, .rejected, .serviceDone, .cancelled:
  110. break
  111. }
  112. stackView.snp.updateConstraints { make in
  113. make.verticalEdges.equalToSuperview().inset((!extraView.isHidden || !actionView.isHidden) ? 6 : 11)
  114. }
  115. }
  116. private func setupViews() {
  117. let container = UIView()
  118. container.backgroundColor = .fill
  119. container.layer.cornerRadius = 12
  120. addSubview(container)
  121. container.snp.makeConstraints { make in
  122. make.horizontalEdges.equalToSuperview().inset(16)
  123. make.bottom.equalToSuperview()
  124. make.top.equalToSuperview().offset(8)
  125. }
  126. stackView.axis = .vertical
  127. stackView.spacing = 6
  128. container.addSubview(stackView)
  129. stackView.snp.makeConstraints { make in
  130. make.horizontalEdges.equalToSuperview().inset(16)
  131. make.verticalEdges.equalToSuperview().inset(11)
  132. }
  133. let orderView = buildOrderView()
  134. stackView.addArrangedSubview(orderView)
  135. let actionView = buildActionView()
  136. stackView.addArrangedSubview(actionView)
  137. let extraView = buildExtraView()
  138. stackView.addArrangedSubview(extraView)
  139. }
  140. private func buildOrderView() -> UIView {
  141. let stackView = UIStackView()
  142. stackView.spacing = 16
  143. stackView.snp.makeConstraints { make in
  144. make.height.equalTo(50)
  145. }
  146. let infoView = buildInfoView()
  147. stackView.addArrangedSubview(infoView)
  148. let replyView = buildReplyView()
  149. stackView.addArrangedSubview(replyView)
  150. let callView = buildCallView()
  151. stackView.addArrangedSubview(callView)
  152. contentArrow.tintColor = .text_4
  153. stackView.addSubview(contentArrow)
  154. contentArrow.snp.makeConstraints { make in
  155. make.centerY.equalToSuperview()
  156. make.trailing.equalToSuperview()
  157. }
  158. return stackView
  159. }
  160. private func buildInfoView() -> UIView {
  161. let container = UIView()
  162. container.onTap { [weak self] in
  163. guard let self else { return }
  164. if curOrder?.buyerUserNo.isMyUid != false {
  165. pushToOrderList()
  166. } else {
  167. pushToOrderRecord()
  168. }
  169. }
  170. container.addSubview(gameIc)
  171. gameIc.snp.makeConstraints { make in
  172. make.leading.equalToSuperview()
  173. make.verticalEdges.equalToSuperview()
  174. make.width.height.equalTo(50)
  175. }
  176. let textView = UIView()
  177. container.addSubview(textView)
  178. textView.snp.makeConstraints { make in
  179. make.centerY.equalToSuperview()
  180. make.leading.equalTo(gameIc.snp.trailing).offset(1)
  181. make.trailing.lessThanOrEqualToSuperview()
  182. }
  183. titleLabel.font = .heading_h5
  184. titleLabel.textColor = .text_5
  185. textView.addSubview(titleLabel)
  186. titleLabel.snp.makeConstraints { make in
  187. make.leading.equalToSuperview()
  188. make.top.equalToSuperview()
  189. }
  190. titleArrow.tintColor = .text_4
  191. textView.addSubview(titleArrow)
  192. titleArrow.snp.makeConstraints { make in
  193. make.leading.equalTo(titleLabel.snp.trailing).offset(5)
  194. make.centerY.equalTo(titleLabel)
  195. make.trailing.lessThanOrEqualToSuperview()
  196. }
  197. descLabel.font = .body_s
  198. descLabel.textColor = .text_3
  199. textView.addSubview(descLabel)
  200. descLabel.snp.makeConstraints { make in
  201. make.horizontalEdges.equalToSuperview()
  202. make.bottom.equalToSuperview()
  203. make.top.equalTo(titleLabel.snp.bottom).offset(2)
  204. }
  205. return container
  206. }
  207. private func buildReplyView() -> UIView {
  208. replyView.isHidden = true
  209. let replyTitleLabel = UILabel()
  210. replyTitleLabel.font = .body_s
  211. replyTitleLabel.text = .init(key: "A00081")
  212. replyTitleLabel.textColor = .text_5
  213. replyTitleLabel.textAlignment = .center
  214. replyView.addSubview(replyTitleLabel)
  215. replyTitleLabel.snp.makeConstraints { make in
  216. make.horizontalEdges.equalToSuperview()
  217. make.top.equalToSuperview()
  218. }
  219. replyRemainLabel.font = .heading_h5
  220. replyRemainLabel.textColor = .text_5
  221. replyView.addSubview(replyRemainLabel)
  222. replyRemainLabel.snp.makeConstraints { make in
  223. make.centerX.equalToSuperview()
  224. make.bottom.equalToSuperview()
  225. make.top.equalTo(replyTitleLabel.snp.bottom).offset(1)
  226. make.leading.greaterThanOrEqualToSuperview()
  227. }
  228. return replyView
  229. }
  230. private func buildCallView() -> UIView {
  231. goCallView.isHidden = true
  232. goCallView.onTap { [weak self] in
  233. guard let self else { return }
  234. guard let uid = viewModel?.userId else { return }
  235. LNIMManager.shared.makeVoiceCall(uid: uid)
  236. }
  237. let callButton = UIButton()
  238. callButton.setTitle(.init(key: "C00016"), for: .normal)
  239. callButton.contentEdgeInsets = .init(top: 0, left: 17, bottom: 0, right: 17)
  240. callButton.titleLabel?.font = .heading_h5
  241. callButton.setTitleColor(.text_1, for: .normal)
  242. callButton.setBackgroundImage(.primary_8, for: .normal)
  243. callButton.layer.cornerRadius = 12
  244. callButton.clipsToBounds = true
  245. callButton.setContentHuggingPriority(.required, for: .horizontal)
  246. callButton.setContentCompressionResistancePriority(.required, for: .horizontal)
  247. callButton.snp.makeConstraints { make in
  248. make.height.equalTo(24)
  249. }
  250. callButton.addAction(UIAction(handler: { [weak self] _ in
  251. guard let self else { return }
  252. guard let uid = viewModel?.userId else { return }
  253. LNIMManager.shared.makeVoiceCall(uid: uid)
  254. }), for: .touchUpInside)
  255. goCallView.addSubview(callButton)
  256. callButton.snp.makeConstraints { make in
  257. make.horizontalEdges.equalToSuperview()
  258. make.centerY.equalToSuperview()
  259. }
  260. return goCallView
  261. }
  262. private func buildActionView() -> UIView {
  263. actionView.snp.makeConstraints { make in
  264. make.height.equalTo(40)
  265. }
  266. let reject = UIButton()
  267. reject.setTitle(.init(key: "A00082"), for: .normal)
  268. reject.setTitleColor(.text_4, for: .normal)
  269. reject.titleLabel?.font = .body_s
  270. reject.contentEdgeInsets = .init(top: 7, left: 40, bottom: 7, right: 40)
  271. reject.layer.cornerRadius = 15
  272. reject.layer.borderWidth = 1
  273. reject.layer.borderColor = .text_3
  274. reject.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  275. reject.addAction(UIAction(handler: { [weak self] _ in
  276. guard let self else { return }
  277. guard let curOrder else { return }
  278. LNOrderManager.shared.rejectOrder(orderId: curOrder.orderId) { success in }
  279. }), for: .touchUpInside)
  280. actionView.addSubview(reject)
  281. reject.snp.makeConstraints { make in
  282. make.leading.equalToSuperview()
  283. make.centerY.equalToSuperview()
  284. make.height.equalTo(30)
  285. }
  286. let accept = UIButton()
  287. accept.setTitle(.init(key: "A00083"), for: .normal)
  288. accept.setTitleColor(.text_1, for: .normal)
  289. accept.titleLabel?.font = .heading_h5
  290. accept.layer.cornerRadius = 15
  291. accept.clipsToBounds = true
  292. accept.setBackgroundImage(.primary_8, for: .normal)
  293. accept.addAction(UIAction(handler: { [weak self] _ in
  294. guard let self else { return }
  295. guard let curOrder else { return }
  296. LNOrderManager.shared.acceptOrder(orderId: curOrder.orderId) { success in }
  297. }), for: .touchUpInside)
  298. actionView.addSubview(accept)
  299. accept.snp.makeConstraints { make in
  300. make.centerY.equalToSuperview()
  301. make.height.equalTo(30)
  302. make.leading.equalTo(reject.snp.trailing).offset(12)
  303. make.trailing.equalToSuperview()
  304. }
  305. return actionView
  306. }
  307. private func buildExtraView() -> UIView {
  308. let line = UIView()
  309. line.backgroundColor = .fill_2
  310. extraView.addSubview(line)
  311. line.snp.makeConstraints { make in
  312. make.horizontalEdges.equalToSuperview().inset(-4)
  313. make.top.equalToSuperview()
  314. make.height.equalTo(0.5)
  315. }
  316. extraLabel.font = .body_xs
  317. extraLabel.textColor = .text_4
  318. extraLabel.numberOfLines = 0
  319. extraView.addSubview(extraLabel)
  320. extraLabel.snp.makeConstraints { make in
  321. make.horizontalEdges.equalToSuperview()
  322. make.top.equalTo(line.snp.bottom).offset(5)
  323. make.bottom.equalToSuperview().offset(-2)
  324. }
  325. return extraView
  326. }
  327. }
  328. #if DEBUG
  329. import SwiftUI
  330. struct LNIMChatGameMateOrderViewPreview: UIViewRepresentable {
  331. func makeUIView(context: Context) -> some UIView {
  332. let container = UIView()
  333. container.backgroundColor = .lightGray
  334. let view = LNIMChatGameMateOrderView()
  335. container.addSubview(view)
  336. view.snp.makeConstraints { make in
  337. make.horizontalEdges.equalToSuperview()
  338. make.centerY.equalToSuperview()
  339. }
  340. return container
  341. }
  342. func updateUIView(_ uiView: UIViewType, context: Context) { }
  343. }
  344. #Preview(body: {
  345. LNIMChatGameMateOrderViewPreview()
  346. })
  347. #endif