LNExchangePanel.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. //
  2. // LNExchangePanel.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2026/1/8.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. enum LNExchangeType {
  11. case coinToDiamond
  12. case diamondToCoin
  13. case beanToCoin
  14. case beanToDiamond
  15. var exchangeType: (from: LNCurrencyType, to: LNCurrencyType) {
  16. switch self {
  17. case .coinToDiamond: (.coin, .diamond)
  18. case .diamondToCoin: (.diamond, .coin)
  19. case .beanToCoin: (.bean, .coin)
  20. case .beanToDiamond: (.bean, .diamond)
  21. }
  22. }
  23. }
  24. class LNExchangePanel: LNPopupView {
  25. private let exchangeType: LNExchangeType
  26. private let exchangeView = UIView()
  27. private let remainLabel = UILabel()
  28. private let exchangeInput = UITextField()
  29. private let expectLabel = UILabel()
  30. private let exchangeButton = UIButton()
  31. private var delayTaskId: String?
  32. init(exchangeType: LNExchangeType) {
  33. self.exchangeType = exchangeType
  34. super.init(frame: .zero)
  35. setupViews()
  36. }
  37. required init?(coder: NSCoder) {
  38. fatalError("init(coder:) has not been implemented")
  39. }
  40. }
  41. extension LNExchangePanel: UITextFieldDelegate {
  42. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  43. let currentText = textField.text ?? ""
  44. guard let range = Range(range, in: currentText) else { return false }
  45. let newText = currentText.replacingCharacters(in: range, with: string)
  46. if newText.isEmpty {
  47. return true
  48. }
  49. if newText.starts(with: "00") {
  50. return false
  51. }
  52. if newText.filter({ $0 == "." }).count > 2 {
  53. return false
  54. }
  55. guard Double(newText) != nil else {
  56. return false
  57. }
  58. return true
  59. }
  60. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  61. textField.resignFirstResponder()
  62. return true
  63. }
  64. }
  65. extension LNExchangePanel {
  66. private func checkExchangeButton() {
  67. let text = exchangeInput.text ?? ""
  68. if text.isEmpty {
  69. exchangeInput.attributedPlaceholder = .init(string: .init(key: "A00261"),
  70. attributes: [.font: UIFont.heading_h3,
  71. .foregroundColor: UIColor.text_2])
  72. } else {
  73. exchangeInput.attributedPlaceholder = nil
  74. }
  75. fetchExchangeResult()
  76. }
  77. private func fetchExchangeResult() {
  78. LNDelayTask.cancel(key: delayTaskId)
  79. exchangeButton.isEnabled = false
  80. exchangeButton.setBackgroundImage(nil, for: .normal)
  81. delayTaskId = LNDelayTask.perform(delay: 1.0) { [weak self] in
  82. guard let self else { return }
  83. let text = exchangeInput.text ?? ""
  84. let value = Double(text) ?? 0
  85. if value == 0 {
  86. expectLabel.text = "0"
  87. return
  88. }
  89. LNPurchaseManager.shared.expectResult(exchangeType: exchangeType, count: value)
  90. { [weak self] result in
  91. guard let self else { return }
  92. guard exchangeInput.text == text else { return }
  93. if let result {
  94. expectLabel.text = "\(result)"
  95. exchangeButton.isEnabled = true
  96. exchangeButton.setBackgroundImage(.primary_8, for: .normal)
  97. } else {
  98. expectLabel.text = ""
  99. }
  100. }
  101. }
  102. }
  103. private func setupViews() {
  104. let titleView = buildTitleView()
  105. container.addSubview(titleView)
  106. titleView.snp.makeConstraints { make in
  107. make.horizontalEdges.equalToSuperview()
  108. make.top.equalToSuperview()
  109. }
  110. let remainView = buildRemain()
  111. container.addSubview(remainView)
  112. remainView.snp.makeConstraints { make in
  113. make.horizontalEdges.equalToSuperview()
  114. make.top.equalTo(titleView.snp.bottom).offset(4)
  115. }
  116. exchangeView.backgroundColor = .fill_1
  117. exchangeView.layer.cornerRadius = 12
  118. container.addSubview(exchangeView)
  119. exchangeView.snp.makeConstraints { make in
  120. make.horizontalEdges.equalToSuperview().inset(16)
  121. make.top.equalTo(remainView.snp.bottom).offset(4)
  122. }
  123. let diamondExchange = buildExchangeView()
  124. exchangeView.addSubview(diamondExchange)
  125. diamondExchange.snp.makeConstraints { make in
  126. make.horizontalEdges.equalToSuperview()
  127. make.top.equalToSuperview()
  128. }
  129. let line = UIView()
  130. line.backgroundColor = .fill_3
  131. exchangeView.addSubview(line)
  132. line.snp.makeConstraints { make in
  133. make.horizontalEdges.equalToSuperview().inset(10)
  134. make.top.equalTo(diamondExchange.snp.bottom).offset(4)
  135. make.height.equalTo(1)
  136. }
  137. let coinView = buildExpectView()
  138. exchangeView.addSubview(coinView)
  139. coinView.snp.makeConstraints { make in
  140. make.horizontalEdges.equalToSuperview()
  141. make.bottom.equalToSuperview()
  142. make.top.equalTo(line.snp.bottom).offset(4)
  143. }
  144. exchangeButton.setTitle(.init(key: "A00262"), for: .normal)
  145. exchangeButton.setTitleColor(.text_1, for: .normal)
  146. exchangeButton.titleLabel?.font = .heading_h3
  147. exchangeButton.backgroundColor = .text_2
  148. exchangeButton.layer.cornerRadius = 23.5
  149. exchangeButton.clipsToBounds = true
  150. exchangeButton.isEnabled = false
  151. exchangeButton.setBackgroundImage(nil, for: .normal)
  152. exchangeButton.addAction(UIAction(handler: { [weak self] _ in
  153. guard let self else { return }
  154. guard let amout = Double(exchangeInput.text ?? "") else { return }
  155. guard amout > 0 else { return }
  156. LNPurchaseManager.shared.exchangeCurrency(exchangeType: exchangeType, amount: amout) { [weak self] success in
  157. guard let self else { return }
  158. guard success else { return }
  159. dismiss()
  160. showToast(.init(key: "A00263"))
  161. }
  162. }), for: .touchUpInside)
  163. container.addSubview(exchangeButton)
  164. exchangeButton.snp.makeConstraints { make in
  165. make.horizontalEdges.equalToSuperview().inset(16)
  166. make.height.equalTo(47)
  167. make.top.equalTo(exchangeView.snp.bottom).offset(24)
  168. make.bottom.equalToSuperview().offset(commonBottomInset)
  169. }
  170. }
  171. private func buildTitleView() -> UIView {
  172. let titleView = UIView()
  173. titleView.isUserInteractionEnabled = false
  174. titleView.snp.makeConstraints { make in
  175. make.height.equalTo(50)
  176. }
  177. let titleLabel = UILabel()
  178. titleLabel.text = switch exchangeType {
  179. case .coinToDiamond, .beanToDiamond: .init(key: "A00264")
  180. case .diamondToCoin, .beanToCoin: .init(key: "A00273")
  181. }
  182. titleLabel.font = .heading_h3
  183. titleLabel.textColor = .text_5
  184. titleView.addSubview(titleLabel)
  185. titleLabel.snp.makeConstraints { make in
  186. make.leading.equalToSuperview().offset(16)
  187. make.centerY.equalToSuperview()
  188. }
  189. return titleView
  190. }
  191. private func buildRemain() -> UIView {
  192. let container = UIView()
  193. container.onTap { [weak self] in
  194. guard let self else { return }
  195. endEditing(true)
  196. }
  197. container.snp.makeConstraints { make in
  198. make.height.equalTo(36)
  199. }
  200. let exchange = UIButton()
  201. exchange.setTitle(.init(key: "A00265"), for: .normal)
  202. exchange.setTitleColor(.text_6, for: .normal)
  203. exchange.titleLabel?.font = .body_xs
  204. exchange.addAction(UIAction(handler: { [weak self] _ in
  205. guard let self else { return }
  206. exchangeInput.text = switch exchangeType {
  207. case .coinToDiamond: myWalletInfo.coin.toDisplay
  208. case .diamondToCoin: myWalletInfo.diamond.toDisplay
  209. case .beanToCoin, .beanToDiamond: myWalletInfo.availableBean.toDisplay
  210. }
  211. checkExchangeButton()
  212. }), for: .touchUpInside)
  213. container.addSubview(exchange)
  214. exchange.snp.makeConstraints { make in
  215. make.centerY.equalToSuperview()
  216. make.trailing.equalToSuperview().offset(-16)
  217. }
  218. let currencyIcon: UIImageView = switch exchangeType {
  219. case .coinToDiamond: .coinImageView()
  220. case .diamondToCoin: .diamondImageView()
  221. case .beanToCoin, .beanToDiamond: .beanImageView()
  222. }
  223. container.addSubview(currencyIcon)
  224. currencyIcon.snp.makeConstraints { make in
  225. make.leading.equalToSuperview().offset(16)
  226. make.centerY.equalToSuperview()
  227. make.width.height.equalTo(24)
  228. }
  229. remainLabel.text = switch exchangeType {
  230. case .coinToDiamond: myWalletInfo.coin.toDisplay
  231. case .diamondToCoin: myWalletInfo.diamond.toDisplay
  232. case .beanToCoin, .beanToDiamond: myWalletInfo.availableBean.toDisplay
  233. }
  234. remainLabel.font = .heading_h1
  235. remainLabel.textColor = .text_5
  236. container.addSubview(remainLabel)
  237. remainLabel.snp.makeConstraints { make in
  238. make.leading.equalTo(currencyIcon.snp.trailing).offset(2)
  239. make.centerY.equalToSuperview()
  240. }
  241. return container
  242. }
  243. private func buildExchangeView() -> UIView {
  244. let container = UIView()
  245. container.onTap { [weak self] in
  246. guard let self else { return }
  247. endEditing(true)
  248. }
  249. container.snp.makeConstraints { make in
  250. make.height.equalTo(67)
  251. }
  252. let titleLabel = UILabel()
  253. titleLabel.text = switch exchangeType {
  254. case .coinToDiamond: .init(key: "A00266")
  255. case .diamondToCoin: .init(key: "A00274")
  256. case .beanToCoin, .beanToDiamond: .init(key: "B00106")
  257. }
  258. titleLabel.font = .heading_h4
  259. titleLabel.textColor = .text_5
  260. container.addSubview(titleLabel)
  261. titleLabel.snp.makeConstraints { make in
  262. make.leading.equalToSuperview().offset(12)
  263. make.centerY.equalToSuperview()
  264. }
  265. exchangeInput.font = .heading_h2
  266. exchangeInput.textColor = .text_5
  267. exchangeInput.visibleView = exchangeView
  268. exchangeInput.delegate = self
  269. exchangeInput.returnKeyType = .done
  270. exchangeInput.attributedPlaceholder = .init(
  271. string: .init(key: "A00261"),
  272. attributes: [.font: UIFont.heading_h3,
  273. .foregroundColor: UIColor.text_2])
  274. exchangeInput.keyboardType = .decimalPad
  275. exchangeInput.addAction(UIAction(handler: { [weak self] _ in
  276. guard let self else { return }
  277. checkExchangeButton()
  278. }), for: .editingChanged)
  279. container.addSubview(exchangeInput)
  280. exchangeInput.snp.makeConstraints { make in
  281. make.trailing.equalToSuperview().offset(-12)
  282. make.centerY.equalToSuperview()
  283. }
  284. let expectIcon: UIImageView = switch exchangeType {
  285. case .coinToDiamond: .coinImageView()
  286. case .diamondToCoin: .diamondImageView()
  287. case .beanToCoin, .beanToDiamond: .beanImageView()
  288. }
  289. container.addSubview(expectIcon)
  290. expectIcon.snp.makeConstraints { make in
  291. make.trailing.equalTo(exchangeInput.snp.leading).offset(-2)
  292. make.centerY.equalToSuperview()
  293. make.width.height.equalTo(20)
  294. }
  295. return container
  296. }
  297. private func buildExpectView() -> UIView {
  298. let container = UIView()
  299. container.snp.makeConstraints { make in
  300. make.height.equalTo(67)
  301. }
  302. let titleLabel = UILabel()
  303. titleLabel.text = switch exchangeType {
  304. case .coinToDiamond, .beanToDiamond: .init(key: "A00267")
  305. case .diamondToCoin, .beanToCoin: .init(key: "A00275")
  306. }
  307. titleLabel.font = .heading_h4
  308. titleLabel.textColor = .text_5
  309. container.addSubview(titleLabel)
  310. titleLabel.snp.makeConstraints { make in
  311. make.leading.equalToSuperview().offset(12)
  312. make.centerY.equalToSuperview()
  313. }
  314. expectLabel.text = "0"
  315. expectLabel.font = .heading_h2
  316. expectLabel.textColor = .text_5
  317. container.addSubview(expectLabel)
  318. expectLabel.snp.makeConstraints { make in
  319. make.trailing.equalToSuperview().offset(-12)
  320. make.centerY.equalToSuperview()
  321. }
  322. let expectIcon: UIImageView = switch exchangeType {
  323. case .coinToDiamond, .beanToDiamond: .diamondImageView()
  324. case .diamondToCoin, .beanToCoin: .coinImageView()
  325. }
  326. container.addSubview(expectIcon)
  327. expectIcon.snp.makeConstraints { make in
  328. make.trailing.equalTo(expectLabel.snp.leading).offset(-2)
  329. make.centerY.equalToSuperview()
  330. make.width.height.equalTo(20)
  331. }
  332. return container
  333. }
  334. }