LNJoinUsSkillFieldsEditView.swift 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. //
  2. // LNJoinUsSkillFieldsEditView.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. class LNJoinUsSkillFieldsEditView: UIView {
  12. private let titleLabel = UILabel()
  13. private let stackView = UIStackView()
  14. private let applyButton = UIButton()
  15. private var fieldViews: [LNSkillFieldBaseEditView] = []
  16. private var field: LNCreateSkillInputFieldsVO?
  17. override var isHidden: Bool {
  18. didSet {
  19. if isHidden {
  20. LNVoicePlayer.shared.stop()
  21. LNVoiceRecorder.shared.stopRecord()
  22. }
  23. }
  24. }
  25. deinit {
  26. LNVoicePlayer.shared.stop()
  27. LNVoiceRecorder.shared.stopRecord()
  28. }
  29. override init(frame: CGRect) {
  30. super.init(frame: frame)
  31. setupViews()
  32. }
  33. func update(_ skill: LNGameCategoryItemVO, info: LNCreateSkillInputFieldsVO) {
  34. stackView.arrangedSubviews.forEach {
  35. stackView.removeArrangedSubview($0)
  36. $0.removeFromSuperview()
  37. }
  38. titleLabel.text = skill.name
  39. if info.bizCategoryCode?.isEmpty != false {
  40. info.bizCategoryCode = skill.code
  41. }
  42. for field in info.fields {
  43. let view: LNSkillFieldBaseEditView? = switch field.type {
  44. case .singleLineText, .number:
  45. LNSkillFieldSingleLineEditView()
  46. case .multiLineText:
  47. LNSkillFieldMultiLineEditView()
  48. case .singleSelection, .multiSelection:
  49. LNSkillFieldSelectionEditView()
  50. case .voice:
  51. LNSkillFieldVoiceEditView()
  52. case .photo:
  53. LNSkillFieldPhotoEditView()
  54. default:
  55. nil
  56. }
  57. if let view {
  58. stackView.addArrangedSubview(view)
  59. fieldViews.append(view)
  60. view.delegate = self
  61. view.update(field)
  62. }
  63. }
  64. field = info
  65. checkSubmitButton()
  66. }
  67. required init?(coder: NSCoder) {
  68. fatalError("init(coder:) has not been implemented")
  69. }
  70. }
  71. extension LNJoinUsSkillFieldsEditView {
  72. private func commit() {
  73. guard let field else { return }
  74. if fieldViews.first(where: { $0.checkAvailable() == false }) != nil {
  75. return
  76. }
  77. showLoading()
  78. DispatchQueue.global().async { [weak self] in
  79. let voices = field.fields.filter({ $0.type == .voice })
  80. let group = DispatchGroup()
  81. var uploadDone = true
  82. for voice in voices {
  83. if let path = voice.value as? String,
  84. !path.starts(with: "http") {
  85. group.enter()
  86. LNFileUploader.shared.startUpload(type: .voice, fileURL: URL(fileURLWithPath: path), progressHandler: nil)
  87. { url, err in
  88. if let err {
  89. showToast(err)
  90. uploadDone = false
  91. } else if let url {
  92. voice.value = url
  93. } else {
  94. uploadDone = false
  95. }
  96. group.leave()
  97. }
  98. }
  99. }
  100. group.wait()
  101. if !uploadDone {
  102. dismissLoading()
  103. return
  104. }
  105. guard let self else { return }
  106. let info = LNCreateSkillFieldsInfo()
  107. info.bizCategoryCode = field.bizCategoryCode!
  108. for item in field.fields {
  109. let config = LNCreateSkillFieldInfo()
  110. config.fieldCode = item.fieldCode
  111. config.value = item.value
  112. if item.type == .voice {
  113. config.duration = item.duration
  114. }
  115. info.fields.append(config)
  116. }
  117. LNGameMateManager.shared.createSkill(info: info) { [weak self] success in
  118. dismissLoading()
  119. guard let self else { return }
  120. guard success else { return }
  121. pushToJoinUsReview()
  122. }
  123. }
  124. }
  125. }
  126. extension LNJoinUsSkillFieldsEditView: LNSkillFieldBaseEditViewDelegate {
  127. func onSkillFieldBaseEditViewInputChanged(view: LNSkillFieldBaseEditView) {
  128. checkSubmitButton()
  129. }
  130. }
  131. extension LNJoinUsSkillFieldsEditView {
  132. private func checkSubmitButton() {
  133. let allInput = fieldViews.first { !$0.hasInput() } == nil
  134. if allInput != applyButton.isEnabled {
  135. applyButton.isEnabled = allInput
  136. applyButton.setBackgroundImage(allInput ? .primary_8 : nil, for: .normal)
  137. }
  138. }
  139. private func setupViews() {
  140. onTap { [weak self] in
  141. guard let self else { return }
  142. endEditing(true)
  143. }
  144. let header = buildTitle()
  145. addSubview(header)
  146. header.snp.makeConstraints { make in
  147. make.horizontalEdges.equalToSuperview()
  148. make.top.equalToSuperview()
  149. }
  150. let scrollView = UIScrollView()
  151. scrollView.showsVerticalScrollIndicator = false
  152. scrollView.showsHorizontalScrollIndicator = false
  153. scrollView.contentInsetAdjustmentBehavior = .never
  154. scrollView.contentInset = .init(top: 0, left: 0, bottom: -commonBottomInset + 62, right: 0)
  155. scrollView.adjustKeyoard()
  156. addSubview(scrollView)
  157. scrollView.snp.makeConstraints { make in
  158. make.horizontalEdges.equalToSuperview().inset(16)
  159. make.top.equalTo(header.snp.bottom)
  160. make.bottom.equalToSuperview()
  161. }
  162. stackView.axis = .vertical
  163. stackView.spacing = 24
  164. scrollView.addSubview(stackView)
  165. stackView.snp.makeConstraints { make in
  166. make.edges.equalToSuperview()
  167. make.width.equalToSuperview()
  168. }
  169. let bottomMenu = UIView()
  170. addSubview(bottomMenu)
  171. bottomMenu.snp.makeConstraints { make in
  172. make.horizontalEdges.equalToSuperview()
  173. make.bottom.equalToSuperview()
  174. }
  175. let bottomGradient = CAGradientLayer()
  176. bottomGradient.colors = [
  177. UIColor.white.withAlphaComponent(0).cgColor,
  178. UIColor.white.cgColor,
  179. UIColor.white.cgColor
  180. ]
  181. bottomGradient.locations = [0, 0.5, 1]
  182. bottomGradient.startPoint = .init(x: 0, y: 0)
  183. bottomGradient.endPoint = .init(x: 0, y: 1)
  184. bottomMenu.layer.addSublayer(bottomGradient)
  185. bottomMenu.publisher(for: \.bounds).removeDuplicates().sink { [weak bottomGradient] newValue in
  186. guard let bottomGradient else { return }
  187. bottomGradient.frame = newValue
  188. }.store(in: &cancellables)
  189. applyButton.setTitle(.init(key: "A00240"), for: .normal)
  190. applyButton.setTitleColor(.text_1, for: .normal)
  191. applyButton.titleLabel?.font = .heading_h3
  192. applyButton.layer.cornerRadius = 23.5
  193. applyButton.backgroundColor = .fill_4
  194. applyButton.clipsToBounds = true
  195. applyButton.isEnabled = false
  196. applyButton.addAction(UIAction(handler: { [weak self] _ in
  197. guard let self else { return }
  198. commit()
  199. }), for: .touchUpInside)
  200. bottomMenu.addSubview(applyButton)
  201. applyButton.snp.makeConstraints { make in
  202. make.horizontalEdges.equalToSuperview().inset(16)
  203. make.bottom.equalToSuperview().offset(commonBottomInset)
  204. make.top.equalToSuperview()
  205. make.height.equalTo(47)
  206. }
  207. }
  208. private func buildTitle() -> UIView {
  209. let container = UIView()
  210. titleLabel.font = .heading_h2
  211. titleLabel.textColor = .text_5
  212. container.addSubview(titleLabel)
  213. titleLabel.snp.makeConstraints { make in
  214. make.horizontalEdges.equalToSuperview().inset(20)
  215. make.verticalEdges.equalToSuperview().inset(20)
  216. }
  217. return container
  218. }
  219. }