// // LNJoinUsSkillFieldsEditView.swift // Gami // // Created by OneeChan on 2026/1/19. // import Foundation import UIKit import SnapKit import Combine class LNJoinUsSkillFieldsEditView: UIView { private let titleLabel = UILabel() private let stackView = UIStackView() private let applyButton = UIButton() private var fieldViews: [LNSkillFieldBaseEditView] = [] private var field: LNCreateSkillInputFieldsVO? override var isHidden: Bool { didSet { if isHidden { LNVoicePlayer.shared.stop() LNVoiceRecorder.shared.stopRecord() } } } deinit { LNVoicePlayer.shared.stop() LNVoiceRecorder.shared.stopRecord() } override init(frame: CGRect) { super.init(frame: frame) setupViews() } func update(_ skill: LNGameCategoryItemVO, info: LNCreateSkillInputFieldsVO) { stackView.arrangedSubviews.forEach { stackView.removeArrangedSubview($0) $0.removeFromSuperview() } titleLabel.text = skill.name if info.bizCategoryCode?.isEmpty != false { info.bizCategoryCode = skill.code } for field in info.fields { let view: LNSkillFieldBaseEditView? = switch field.type { case .singleLineText, .number: LNSkillFieldSingleLineEditView() case .multiLineText: LNSkillFieldMultiLineEditView() case .singleSelection, .multiSelection: LNSkillFieldSelectionEditView() case .voice: LNSkillFieldVoiceEditView() case .photo: LNSkillFieldPhotoEditView() default: nil } if let view { stackView.addArrangedSubview(view) fieldViews.append(view) view.delegate = self view.update(field) } } field = info checkSubmitButton() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNJoinUsSkillFieldsEditView { private func commit() { guard let field else { return } if fieldViews.first(where: { $0.checkAvailable() == false }) != nil { return } showLoading() DispatchQueue.global().async { [weak self] in let voices = field.fields.filter({ $0.type == .voice }) let group = DispatchGroup() var uploadDone = true for voice in voices { if let path = voice.value as? String, !path.starts(with: "http") { group.enter() LNFileUploader.shared.startUpload(type: .voice, fileURL: URL(fileURLWithPath: path), progressHandler: nil) { url, err in if let err { showToast(err) uploadDone = false } else if let url { voice.value = url } else { uploadDone = false } group.leave() } } } group.wait() if !uploadDone { dismissLoading() return } guard let self else { return } let info = LNCreateSkillFieldsInfo() info.bizCategoryCode = field.bizCategoryCode! for item in field.fields { let config = LNCreateSkillFieldInfo() config.fieldCode = item.fieldCode config.value = item.value if item.type == .voice { config.duration = item.duration } info.fields.append(config) } LNGameMateManager.shared.createSkill(info: info) { [weak self] success in dismissLoading() guard let self else { return } guard success else { return } pushToJoinUsReview() } } } } extension LNJoinUsSkillFieldsEditView: LNSkillFieldBaseEditViewDelegate { func onSkillFieldBaseEditViewInputChanged(view: LNSkillFieldBaseEditView) { checkSubmitButton() } } extension LNJoinUsSkillFieldsEditView { private func checkSubmitButton() { let allInput = fieldViews.first { !$0.hasInput() } == nil if allInput != applyButton.isEnabled { applyButton.isEnabled = allInput applyButton.setBackgroundImage(allInput ? .primary_8 : nil, for: .normal) } } private func setupViews() { onTap { [weak self] in guard let self else { return } endEditing(true) } let header = buildTitle() addSubview(header) header.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalToSuperview() } let scrollView = UIScrollView() scrollView.showsVerticalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false scrollView.contentInsetAdjustmentBehavior = .never scrollView.contentInset = .init(top: 0, left: 0, bottom: -commonBottomInset + 62, right: 0) scrollView.adjustKeyoard() addSubview(scrollView) scrollView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalTo(header.snp.bottom) make.bottom.equalToSuperview() } stackView.axis = .vertical stackView.spacing = 24 scrollView.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() make.width.equalToSuperview() } let bottomMenu = UIView() addSubview(bottomMenu) bottomMenu.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.bottom.equalToSuperview() } let bottomGradient = CAGradientLayer() bottomGradient.colors = [ UIColor.white.withAlphaComponent(0).cgColor, UIColor.white.cgColor, UIColor.white.cgColor ] bottomGradient.locations = [0, 0.5, 1] bottomGradient.startPoint = .init(x: 0, y: 0) bottomGradient.endPoint = .init(x: 0, y: 1) bottomMenu.layer.addSublayer(bottomGradient) bottomMenu.publisher(for: \.bounds).removeDuplicates().sink { [weak bottomGradient] newValue in guard let bottomGradient else { return } bottomGradient.frame = newValue }.store(in: &cancellables) applyButton.setTitle(.init(key: "A00240"), for: .normal) applyButton.setTitleColor(.text_1, for: .normal) applyButton.titleLabel?.font = .heading_h3 applyButton.layer.cornerRadius = 23.5 applyButton.backgroundColor = .fill_4 applyButton.clipsToBounds = true applyButton.isEnabled = false applyButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } commit() }), for: .touchUpInside) bottomMenu.addSubview(applyButton) applyButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.bottom.equalToSuperview().offset(commonBottomInset) make.top.equalToSuperview() make.height.equalTo(47) } } private func buildTitle() -> UIView { let container = UIView() titleLabel.font = .heading_h2 titleLabel.textColor = .text_5 container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(20) make.verticalEdges.equalToSuperview().inset(20) } return container } }