LNJoinUsInputPhoneView.swift 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. //
  2. // LNJoinUsInputPhoneView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/1/19.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. protocol LNJoinUsInputPhoneViewDelegate: AnyObject {
  11. func joinUsInputPhoneView(view: LNJoinUsInputPhoneView, didFinished code: String, phone: String)
  12. }
  13. class LNJoinUsInputPhoneView: UIView {
  14. private let countryIcon = UIImageView()
  15. private let countryCodeLabel = UILabel()
  16. private let phoneInputView = LNTextField()
  17. private let confirmButton = UIButton()
  18. private var sections: [(code: String, list: [LNCountryCodeVO])] = []
  19. weak var delegate: LNJoinUsInputPhoneViewDelegate?
  20. override init(frame: CGRect) {
  21. super.init(frame: frame)
  22. setupViews()
  23. LNEventDeliver.addObserver(self)
  24. loadCountryCodeList()
  25. }
  26. required init?(coder: NSCoder) {
  27. fatalError("init(coder:) has not been implemented")
  28. }
  29. }
  30. extension LNJoinUsInputPhoneView {
  31. private func loadCountryCodeList() {
  32. LNConfigManager.shared.getCountryCodeList { [weak self] list in
  33. guard let self else { return }
  34. guard let list else { return }
  35. sortList(list)
  36. }
  37. }
  38. private func sortList(_ list: [LNCountryCodeVO]) {
  39. sections.removeAll()
  40. var map: [String: [LNCountryCodeVO]] = [:]
  41. for item in list {
  42. let first = item.name.classificationFirstLetter
  43. var section = map[first] ?? []
  44. section.append(item)
  45. map[first] = section
  46. }
  47. let keys = map.keys.sorted()
  48. for key in keys {
  49. sections.append((key, map[key]) as! (code: String, list: [LNCountryCodeVO]))
  50. }
  51. var item: LNCountryCodeVO? = list.first {
  52. $0.code == LNAppConfig.shared.curLang.countryCode
  53. }
  54. if item == nil {
  55. item = list.first {
  56. $0.code == LNAppLanguage.indonesian.countryCode
  57. }
  58. }
  59. if let item {
  60. countryIcon.sd_setImage(with: URL(string: item.icon))
  61. countryCodeLabel.text = item.num
  62. }
  63. }
  64. }
  65. extension LNJoinUsInputPhoneView: LNProfileManagerNotify {
  66. func onBindPhoneCaptchaCoolDownChanged(time: Int) {
  67. let text: String = if time == 0 {
  68. .init(key: "B00023")
  69. } else {
  70. .init(key: "B00023") + " (\(time)s)"
  71. }
  72. confirmButton.setTitle(text, for: .normal)
  73. checkConfirmButton()
  74. }
  75. }
  76. extension LNJoinUsInputPhoneView {
  77. private func checkConfirmButton() {
  78. let text = phoneInputView.text ?? ""
  79. confirmButton.isEnabled = !text.isEmpty && LNProfileManager.shared.canSendCaptcha
  80. }
  81. private func setupViews() {
  82. onTap { [weak self] in
  83. guard let self else { return }
  84. endEditing(true)
  85. }
  86. let titleLabel = UILabel()
  87. titleLabel.font = .heading_h2
  88. titleLabel.textColor = .text_5
  89. titleLabel.text = .init(key: "B00035")
  90. titleLabel.textAlignment = .center
  91. addSubview(titleLabel)
  92. titleLabel.snp.makeConstraints { make in
  93. make.horizontalEdges.equalToSuperview().inset(16)
  94. make.top.equalToSuperview().offset(26)
  95. }
  96. let phoneInput = buildPhoneInput()
  97. addSubview(phoneInput)
  98. phoneInput.snp.makeConstraints { make in
  99. make.horizontalEdges.equalToSuperview().inset(22)
  100. make.top.equalTo(titleLabel.snp.bottom).offset(30)
  101. }
  102. let confirm = buildConfirmButton()
  103. addSubview(confirm)
  104. confirm.snp.makeConstraints { make in
  105. make.horizontalEdges.equalToSuperview().inset(22)
  106. make.top.equalTo(phoneInput.snp.bottom).offset(24)
  107. }
  108. }
  109. private func buildPhoneInput() -> UIView {
  110. let container = UIView()
  111. container.backgroundColor = .fill_2
  112. container.layer.cornerRadius = 26
  113. container.snp.makeConstraints { make in
  114. make.height.equalTo(52)
  115. }
  116. let countryView = UIView()
  117. countryView.onTap { [weak self] in
  118. guard let self else { return }
  119. guard !sections.isEmpty else { return }
  120. let panel = LNCountrySelectPanel()
  121. panel.containerHeight = .height(superview!.bounds.height)
  122. panel.update(sections)
  123. panel.handler = { [weak self] item in
  124. guard let self else { return }
  125. countryIcon.sd_setImage(with: URL(string: item.icon))
  126. countryCodeLabel.text = item.num
  127. }
  128. panel.popup()
  129. }
  130. container.addSubview(countryView)
  131. countryView.snp.makeConstraints { make in
  132. make.verticalEdges.equalToSuperview()
  133. make.leading.equalToSuperview()
  134. }
  135. countryIcon.layer.cornerRadius = 12
  136. countryIcon.clipsToBounds = true
  137. countryView.addSubview(countryIcon)
  138. countryIcon.snp.makeConstraints { make in
  139. make.centerY.equalToSuperview()
  140. make.leading.equalToSuperview().offset(16)
  141. make.width.height.equalTo(24)
  142. }
  143. countryCodeLabel.font = .heading_h2
  144. countryCodeLabel.textColor = .text_5
  145. countryCodeLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
  146. countryCodeLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
  147. countryView.addSubview(countryCodeLabel)
  148. countryCodeLabel.snp.makeConstraints { make in
  149. make.centerY.equalToSuperview()
  150. make.leading.equalTo(countryIcon.snp.trailing).offset(4)
  151. }
  152. let config = UIImage.SymbolConfiguration(pointSize: 10)
  153. let arrow = UIImageView()
  154. arrow.image = .init(systemName: "chevron.down", withConfiguration: config)?.withRenderingMode(.alwaysTemplate)
  155. arrow.tintColor = .text_5
  156. countryView.addSubview(arrow)
  157. arrow.snp.makeConstraints { make in
  158. make.centerY.equalToSuperview()
  159. make.leading.equalTo(countryCodeLabel.snp.trailing).offset(4)
  160. make.trailing.equalToSuperview()
  161. make.width.equalTo(12)
  162. }
  163. phoneInputView.font = .heading_h2
  164. phoneInputView.textColor = .text_5
  165. phoneInputView.attributedPlaceholder = .init(string: .init(key: "B00021"), attributes: [
  166. .font: UIFont.body_l,
  167. .foregroundColor: UIColor.text_2
  168. ])
  169. phoneInputView.clearButtonMode = .always
  170. phoneInputView.keyboardType = .numberPad
  171. phoneInputView.cursorWidth = 2
  172. phoneInputView.cursorHeight = 18
  173. phoneInputView.addAction(UIAction(handler: { [weak self] _ in
  174. guard let self else { return }
  175. checkConfirmButton()
  176. }), for: .editingChanged)
  177. container.addSubview(phoneInputView)
  178. phoneInputView.snp.makeConstraints { make in
  179. make.centerY.equalToSuperview()
  180. make.leading.equalTo(countryView.snp.trailing).offset(12)
  181. make.trailing.equalToSuperview().offset(-12)
  182. }
  183. return container
  184. }
  185. private func buildConfirmButton() -> UIView {
  186. confirmButton.setBackgroundImage(.primary_8, for: .normal)
  187. confirmButton.layer.cornerRadius = 23.5
  188. confirmButton.clipsToBounds = true
  189. confirmButton.setTitle(.init(key: "B00023"), for: .normal)
  190. confirmButton.setTitleColor(.text_1, for: .normal)
  191. confirmButton.titleLabel?.font = .heading_h3
  192. confirmButton.isEnabled = false
  193. confirmButton.addAction(UIAction(handler: { [weak self] _ in
  194. guard let self else { return }
  195. let code = countryCodeLabel.text
  196. let phone = phoneInputView.text
  197. guard let code, let phone else { return }
  198. showLoading()
  199. LNProfileManager.shared.getBindPhoneCaptcha(code: code, phone: phone)
  200. { [weak self] success in
  201. dismissLoading()
  202. guard let self else { return }
  203. guard success else { return }
  204. delegate?.joinUsInputPhoneView(view: self, didFinished: code, phone: phone)
  205. }
  206. }), for: .touchUpInside)
  207. confirmButton.snp.makeConstraints { make in
  208. make.height.equalTo(47)
  209. }
  210. return confirmButton
  211. }
  212. }