LNJoinUsInputInfoView.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. //
  2. // LNJoinUsInputInfoView.swift
  3. // Gami
  4. //
  5. // Created by OneeChan on 2026/1/19.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. import Combine
  11. protocol LNJoinUsInputInfoViewDelegate: AnyObject {
  12. func joinUsInputInfoViewDidFinish(view: LNJoinUsInputInfoView)
  13. }
  14. class LNJoinUsInputInfoView: UIView {
  15. private let scrollView = UIScrollView()
  16. private let avatar = LNImageUploadView()
  17. private let nameInput = UITextField()
  18. private let nameCountLabel = UILabel()
  19. private let maleButton = UIButton()
  20. private let femaleButton = UIButton()
  21. private var gender: LNUserGender = .unknow {
  22. didSet {
  23. maleButton.backgroundColor = gender == .male ? .fill_5 : .primary_1
  24. femaleButton.backgroundColor = gender == .female ? .fill_5 : .primary_1
  25. }
  26. }
  27. private let birthdayLabel = UILabel()
  28. private var birthday: Double = 0 {
  29. didSet {
  30. birthdayLabel.text = birthday.formattedFullDate("-")
  31. }
  32. }
  33. private let languageLabel = UILabel()
  34. private var curLanguages: [LNLanguageConstsVO] = [] {
  35. didSet {
  36. languageLabel.text = curLanguages.map({ $0.name }).joined(separator: ",")
  37. }
  38. }
  39. private let bioInput = LNCommonTextView()
  40. private let applyButton = UIButton()
  41. weak var delegate: LNJoinUsInputInfoViewDelegate?
  42. override init(frame: CGRect) {
  43. super.init(frame: frame)
  44. setupViews()
  45. }
  46. func update(_ info: LNJoinUsBaseInfoVO) {
  47. avatar.loadImage(url:info.avatar)
  48. nameInput.text = info.nickname
  49. gender = info.gender
  50. bioInput.setText(info.intro)
  51. var languages: [LNLanguageConstsVO] = []
  52. for language in info.languageCodes {
  53. if let item = LNConfigManager.shared.commonConfig.commonLanguageConsts.first(where: { $0.code == language }) {
  54. languages.append(item)
  55. }
  56. }
  57. curLanguages = languages
  58. let dateFormatter = DateFormatter()
  59. dateFormatter.dateFormat = "yyyy-MM-dd"
  60. if let date = dateFormatter.date(from: info.birthday) {
  61. birthday = date.timeIntervalSince1970
  62. } else {
  63. birthday = Double(myUserInfo.birthday / 1_000)
  64. }
  65. checkSaveButton()
  66. }
  67. required init?(coder: NSCoder) {
  68. fatalError("init(coder:) has not been implemented")
  69. }
  70. }
  71. extension LNJoinUsInputInfoView: LNImageUploadViewDelegate {
  72. func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) {
  73. checkSaveButton()
  74. }
  75. }
  76. extension LNJoinUsInputInfoView: UITextFieldDelegate, UITextViewDelegate {
  77. func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
  78. let currentText = textField.text ?? ""
  79. guard let range = Range(range, in: currentText) else { return false }
  80. let newText = currentText.replacingCharacters(in: range, with: string)
  81. if newText.count < currentText.count {
  82. return true
  83. }
  84. return newText.count <= LNProfileManager.nameMaxInput
  85. }
  86. func textFieldShouldReturn(_ textField: UITextField) -> Bool {
  87. textField.resignFirstResponder()
  88. return true
  89. }
  90. func textViewDidChange(_ textView: UITextView) {
  91. checkSaveButton()
  92. }
  93. }
  94. extension LNJoinUsInputInfoView {
  95. private func checkSaveButton() {
  96. let allInput = avatar.imageUrl?.isEmpty == false
  97. && nameInput.text?.isEmpty == false
  98. && gender != .unknow
  99. && birthday != 0
  100. && !curLanguages.isEmpty
  101. && bioInput.textView.text.isEmpty == false
  102. if allInput != applyButton.isEnabled {
  103. applyButton.isEnabled = allInput
  104. applyButton.setBackgroundImage(allInput ? .primary_8 : nil, for: .normal)
  105. }
  106. }
  107. private func setupViews() {
  108. onTap { [weak self] in
  109. guard let self else { return }
  110. endEditing(true)
  111. }
  112. scrollView.showsVerticalScrollIndicator = false
  113. scrollView.showsHorizontalScrollIndicator = false
  114. scrollView.contentInset = .init(top: 0, left: 0, bottom: -commonBottomInset + 32 + 47, right: 0)
  115. scrollView.adjustKeyoard()
  116. addSubview(scrollView)
  117. scrollView.snp.makeConstraints { make in
  118. make.horizontalEdges.equalToSuperview().inset(16)
  119. make.top.equalToSuperview().offset(20)
  120. make.bottom.equalToSuperview()
  121. }
  122. let stackView = UIStackView()
  123. stackView.axis = .vertical
  124. stackView.spacing = 24
  125. scrollView.addSubview(stackView)
  126. stackView.snp.makeConstraints { make in
  127. make.edges.equalToSuperview()
  128. make.width.equalToSuperview()
  129. }
  130. stackView.addArrangedSubview(buildAvatarView())
  131. stackView.addArrangedSubview(buildNickNameView())
  132. stackView.addArrangedSubview(buildGenderView())
  133. stackView.addArrangedSubview(buildBirthdayView())
  134. stackView.addArrangedSubview(buildLanguageView())
  135. stackView.addArrangedSubview(buildBioView())
  136. let bottomMenu = UIView()
  137. addSubview(bottomMenu)
  138. bottomMenu.snp.makeConstraints { make in
  139. make.horizontalEdges.equalToSuperview()
  140. make.bottom.equalToSuperview()
  141. }
  142. let bottomGradient = CAGradientLayer()
  143. bottomGradient.colors = [
  144. UIColor.white.withAlphaComponent(0).cgColor,
  145. UIColor.white.cgColor,
  146. UIColor.white.cgColor
  147. ]
  148. bottomGradient.locations = [0, 0.5, 1]
  149. bottomGradient.startPoint = .init(x: 0, y: 0)
  150. bottomGradient.endPoint = .init(x: 0, y: 1)
  151. bottomMenu.layer.addSublayer(bottomGradient)
  152. bottomMenu.publisher(for: \.bounds).removeDuplicates().sink { [weak bottomGradient] newValue in
  153. guard let bottomGradient else { return }
  154. bottomGradient.frame = newValue
  155. }.store(in: &cancellables)
  156. applyButton.setTitle(.init(key: "B00045"), for: .normal)
  157. applyButton.setTitleColor(.text_1, for: .normal)
  158. applyButton.titleLabel?.font = .heading_h3
  159. applyButton.layer.cornerRadius = 23.5
  160. applyButton.backgroundColor = .fill_4
  161. applyButton.clipsToBounds = true
  162. applyButton.addAction(UIAction(handler: { [weak self] _ in
  163. guard let self else { return }
  164. guard let avatarUrl = avatar.imageUrl,
  165. let name = nameInput.text else {
  166. return
  167. }
  168. let config = LNJoinUsBaseInfoVO()
  169. config.avatar = avatarUrl
  170. config.nickname = name
  171. config.gender = gender
  172. config.birthday = birthday.formattedFullDate("-", normal: true)
  173. config.languageCodes = curLanguages.map({ $0.code })
  174. config.intro = bioInput.textView.text
  175. LNGameMateManager.shared.setJoinGameMateBaseInfo(info: config) { [weak self] success in
  176. guard let self, success else { return }
  177. delegate?.joinUsInputInfoViewDidFinish(view: self)
  178. }
  179. }), for: .touchUpInside)
  180. bottomMenu.addSubview(applyButton)
  181. applyButton.snp.makeConstraints { make in
  182. make.horizontalEdges.equalToSuperview().inset(16)
  183. make.bottom.equalToSuperview().offset(commonBottomInset)
  184. make.top.equalToSuperview()
  185. make.height.equalTo(47)
  186. }
  187. }
  188. private func buildAvatarView() -> UIView {
  189. let groupView = LNJoinUsInputFieldGroupView()
  190. groupView.titleLabel.text = .init(key: "B00036")
  191. groupView.descLabel.text = .init(key: "B00037")
  192. groupView.showExample {
  193. let panel = LNJoinUsAvatarExamplePanel()
  194. panel.popup()
  195. }
  196. avatar.uploadType = .avatar
  197. avatar.backgroundColor = .fill_2
  198. avatar.layer.cornerRadius = 11
  199. avatar.clipsToBounds = true
  200. avatar.contentMode = .scaleAspectFill
  201. avatar.showClearButton = false
  202. avatar.showDefault = true
  203. avatar.delegate = self
  204. avatar.onTap { [weak self] in
  205. guard let self else { return }
  206. LNBottomSheetMenu.showImageSelectMenu(view: self, options: .init(allowEdit: true))
  207. { [weak self] image, _ in
  208. guard let self else { return }
  209. guard let image = image?.compress(type: .avatar) else { return }
  210. avatar.uploadImage(image: image)
  211. }
  212. }
  213. groupView.container.addSubview(avatar)
  214. avatar.snp.makeConstraints { make in
  215. make.centerX.equalToSuperview()
  216. make.verticalEdges.equalToSuperview()
  217. make.width.height.equalTo(105)
  218. }
  219. return groupView
  220. }
  221. private func buildNickNameView() -> UIView {
  222. let groupView = LNJoinUsInputFieldGroupView()
  223. groupView.titleLabel.text = .init(key: "A00188")
  224. groupView.descLabel.text = .init(key: "B00038")
  225. let container = UIView()
  226. container.backgroundColor = .fill_2
  227. container.layer.cornerRadius = 19
  228. groupView.container.addSubview(container)
  229. container.snp.makeConstraints { make in
  230. make.edges.equalToSuperview()
  231. make.height.equalTo(38)
  232. }
  233. nameCountLabel.font = .body_m
  234. nameCountLabel.textColor = .text_2
  235. container.addSubview(nameCountLabel)
  236. nameCountLabel.snp.makeConstraints { make in
  237. make.centerY.equalToSuperview()
  238. make.trailing.equalToSuperview().offset(-16)
  239. }
  240. nameInput.font = .body_m
  241. nameInput.textColor = .text_5
  242. nameInput.placeholder = .init(key: "B00043")
  243. nameInput.delegate = self
  244. nameInput.addAction(UIAction(handler: { [weak self] _ in
  245. guard let self else { return }
  246. nameCountLabel.text = "\(nameInput.text?.count ?? 0)/\(LNProfileManager.nameMaxInput)"
  247. }), for: .editingChanged)
  248. container.addSubview(nameInput)
  249. nameInput.snp.makeConstraints { make in
  250. make.leading.equalToSuperview().offset(16)
  251. make.centerY.equalToSuperview()
  252. make.trailing.equalTo(nameCountLabel.snp.leading).offset(-16)
  253. }
  254. return groupView
  255. }
  256. private func buildGenderView() -> UIView {
  257. let groupView = LNJoinUsInputFieldGroupView()
  258. groupView.titleLabel.text = .init(key: "A00032")
  259. groupView.descLabel.text = .init(key: "B00039")
  260. let stackView = UIStackView()
  261. stackView.spacing = 23
  262. stackView.distribution = .fillEqually
  263. groupView.container.addSubview(stackView)
  264. stackView.snp.makeConstraints { make in
  265. make.edges.equalToSuperview()
  266. }
  267. maleButton.setImage(.icGenderMale, for: .normal)
  268. maleButton.setTitle(.init(key: "A00014"), for: .normal)
  269. maleButton.setTitleColor(.text_5, for: .normal)
  270. maleButton.titleLabel?.font = .heading_h4
  271. maleButton.backgroundColor = .primary_1
  272. maleButton.layer.cornerRadius = 20
  273. maleButton.titleEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 0)
  274. maleButton.addAction(UIAction(handler: { [weak self] _ in
  275. guard let self else { return }
  276. gender = .male
  277. checkSaveButton()
  278. }), for: .touchUpInside)
  279. stackView.addArrangedSubview(maleButton)
  280. maleButton.snp.makeConstraints { make in
  281. make.height.equalTo(40)
  282. }
  283. femaleButton.setImage(.icGenderFemale, for: .normal)
  284. femaleButton.setTitle(.init(key: "A00015"), for: .normal)
  285. femaleButton.setTitleColor(.text_5, for: .normal)
  286. femaleButton.titleLabel?.font = .heading_h4
  287. femaleButton.backgroundColor = .primary_1
  288. femaleButton.layer.cornerRadius = 20
  289. femaleButton.titleEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 0)
  290. femaleButton.addAction(UIAction(handler: { [weak self] _ in
  291. guard let self else { return }
  292. gender = .female
  293. checkSaveButton()
  294. }), for: .touchUpInside)
  295. stackView.addArrangedSubview(femaleButton)
  296. femaleButton.snp.makeConstraints { make in
  297. make.height.equalTo(40)
  298. }
  299. return groupView
  300. }
  301. private func buildBirthdayView() -> UIView {
  302. let groupView = LNJoinUsInputFieldGroupView()
  303. groupView.titleLabel.text = .init(key: "A00205")
  304. groupView.descLabel.text = .init(key: "B00040")
  305. let container = UIView()
  306. container.backgroundColor = .fill_2
  307. container.layer.cornerRadius = 19
  308. container.onTap { [weak self] in
  309. guard let self else { return }
  310. let panel = LNDatePickerPanel()
  311. panel.setDefault(birthday)
  312. panel.handler = { [weak self] date in
  313. guard let self else { return }
  314. birthday = date
  315. checkSaveButton()
  316. }
  317. panel.popup()
  318. }
  319. groupView.container.addSubview(container)
  320. container.snp.makeConstraints { make in
  321. make.edges.equalToSuperview()
  322. make.height.equalTo(38)
  323. }
  324. let arrow = UIImageView.arrowImageView(size: 15)
  325. arrow.tintColor = .text_3
  326. container.addSubview(arrow)
  327. arrow.snp.makeConstraints { make in
  328. make.centerY.equalToSuperview()
  329. make.trailing.equalToSuperview().offset(-16)
  330. }
  331. birthdayLabel.font = .heading_h4
  332. birthdayLabel.textColor = .text_5
  333. container.addSubview(birthdayLabel)
  334. birthdayLabel.snp.makeConstraints { make in
  335. make.leading.equalToSuperview().offset(16)
  336. make.centerY.equalToSuperview()
  337. }
  338. return groupView
  339. }
  340. private func buildLanguageView() -> UIView {
  341. let groupView = LNJoinUsInputFieldGroupView()
  342. groupView.titleLabel.text = .init(key: "A00256")
  343. groupView.descLabel.isHidden = true
  344. let container = UIView()
  345. container.backgroundColor = .fill_2
  346. container.layer.cornerRadius = 19
  347. container.onTap { [weak self] in
  348. guard let self else { return }
  349. let panel = LNLanguageSelectPanel()
  350. panel.update(curSelect: curLanguages)
  351. panel.handler = { [weak self] languages in
  352. guard let self else { return }
  353. curLanguages = languages
  354. checkSaveButton()
  355. }
  356. panel.popup()
  357. }
  358. groupView.container.addSubview(container)
  359. container.snp.makeConstraints { make in
  360. make.edges.equalToSuperview()
  361. make.height.equalTo(38)
  362. }
  363. let arrow = UIImageView.arrowImageView(size: 15)
  364. arrow.tintColor = .text_3
  365. container.addSubview(arrow)
  366. arrow.snp.makeConstraints { make in
  367. make.centerY.equalToSuperview()
  368. make.trailing.equalToSuperview().offset(-16)
  369. }
  370. languageLabel.font = .heading_h4
  371. languageLabel.textColor = .text_5
  372. container.addSubview(languageLabel)
  373. languageLabel.snp.makeConstraints { make in
  374. make.leading.equalToSuperview().offset(16)
  375. make.centerY.equalToSuperview()
  376. }
  377. return groupView
  378. }
  379. private func buildBioView() -> UIView {
  380. let groupView = LNJoinUsInputFieldGroupView()
  381. groupView.titleLabel.text = .init(key: "B00042")
  382. groupView.descLabel.text = .init(key: "B00041")
  383. bioInput.placeholderLabel.text = .init(key: "B00044")
  384. bioInput.maxInput = LNProfileManager.bioMaxInput
  385. bioInput.delegate = self
  386. groupView.container.addSubview(bioInput)
  387. bioInput.snp.makeConstraints { make in
  388. make.edges.equalToSuperview()
  389. make.height.equalTo(130)
  390. }
  391. return groupView
  392. }
  393. }
  394. #if DEBUG
  395. import SwiftUI
  396. struct LNJoinUsInputInfoViewPreview: UIViewRepresentable {
  397. func makeUIView(context: Context) -> some UIView {
  398. let container = UIView()
  399. container.backgroundColor = .fill
  400. let view = LNJoinUsInputInfoView()
  401. container.addSubview(view)
  402. view.snp.makeConstraints { make in
  403. make.edges.equalToSuperview()
  404. }
  405. return container
  406. }
  407. func updateUIView(_ uiView: UIViewType, context: Context) { }
  408. }
  409. #Preview(body: {
  410. LNJoinUsInputInfoViewPreview()
  411. })
  412. #endif