// // LNJoinUsInputInfoView.swift // Gami // // Created by OneeChan on 2026/1/19. // import Foundation import UIKit import SnapKit import Combine protocol LNJoinUsInputInfoViewDelegate: AnyObject { func joinUsInputInfoViewDidFinish(view: LNJoinUsInputInfoView) } class LNJoinUsInputInfoView: UIView { private let scrollView = UIScrollView() private let avatar = LNImageUploadView() private let nameInput = UITextField() private let nameCountLabel = UILabel() private let maleButton = UIButton() private let femaleButton = UIButton() private var gender: LNUserGender = .unknow { didSet { maleButton.backgroundColor = gender == .male ? .fill_5 : .primary_1 femaleButton.backgroundColor = gender == .female ? .fill_5 : .primary_1 } } private let birthdayLabel = UILabel() private var birthday: Double = 0 { didSet { birthdayLabel.text = birthday.formattedFullDate("-") } } private let languageLabel = UILabel() private var curLanguages: [LNLanguageConstsVO] = [] { didSet { languageLabel.text = curLanguages.map({ $0.name }).joined(separator: ",") } } private let bioInput = LNCommonTextView() private let applyButton = UIButton() weak var delegate: LNJoinUsInputInfoViewDelegate? override init(frame: CGRect) { super.init(frame: frame) setupViews() } func update(_ info: LNJoinUsBaseInfoVO) { avatar.loadImage(url:info.avatar) nameInput.text = info.nickname gender = info.gender bioInput.setText(info.intro) var languages: [LNLanguageConstsVO] = [] for language in info.languageCodes { if let item = LNConfigManager.shared.commonConfig.commonLanguageConsts.first(where: { $0.code == language }) { languages.append(item) } } curLanguages = languages let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" if let date = dateFormatter.date(from: info.birthday) { birthday = date.timeIntervalSince1970 } else { birthday = Double(myUserInfo.birthday / 1_000) } checkSaveButton() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNJoinUsInputInfoView: LNImageUploadViewDelegate { func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) { checkSaveButton() } } extension LNJoinUsInputInfoView: UITextFieldDelegate, UITextViewDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let currentText = textField.text ?? "" guard let range = Range(range, in: currentText) else { return false } let newText = currentText.replacingCharacters(in: range, with: string) if newText.count < currentText.count { return true } return newText.count <= LNProfileManager.nameMaxInput } func textFieldShouldReturn(_ textField: UITextField) -> Bool { textField.resignFirstResponder() return true } func textViewDidChange(_ textView: UITextView) { checkSaveButton() } } extension LNJoinUsInputInfoView { private func checkSaveButton() { let allInput = avatar.imageUrl?.isEmpty == false && nameInput.text?.isEmpty == false && gender != .unknow && birthday != 0 && !curLanguages.isEmpty && bioInput.textView.text.isEmpty == false 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) } scrollView.showsVerticalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false scrollView.contentInset = .init(top: 0, left: 0, bottom: -commonBottomInset + 32 + 47, right: 0) scrollView.adjustKeyoard() addSubview(scrollView) scrollView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.top.equalToSuperview().offset(20) make.bottom.equalToSuperview() } let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 24 scrollView.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() make.width.equalToSuperview() } stackView.addArrangedSubview(buildAvatarView()) stackView.addArrangedSubview(buildNickNameView()) stackView.addArrangedSubview(buildGenderView()) stackView.addArrangedSubview(buildBirthdayView()) stackView.addArrangedSubview(buildLanguageView()) stackView.addArrangedSubview(buildBioView()) 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: "B00045"), 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.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } guard let avatarUrl = avatar.imageUrl, let name = nameInput.text else { return } let config = LNJoinUsBaseInfoVO() config.avatar = avatarUrl config.nickname = name config.gender = gender config.birthday = birthday.formattedFullDate("-", normal: true) config.languageCodes = curLanguages.map({ $0.code }) config.intro = bioInput.textView.text LNGameMateManager.shared.setJoinGameMateBaseInfo(info: config) { [weak self] success in guard let self, success else { return } delegate?.joinUsInputInfoViewDidFinish(view: self) } }), 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 buildAvatarView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "B00036") groupView.descLabel.text = .init(key: "B00037") groupView.showExample { let panel = LNJoinUsAvatarExamplePanel() panel.popup() } avatar.uploadType = .avatar avatar.backgroundColor = .fill_2 avatar.layer.cornerRadius = 11 avatar.clipsToBounds = true avatar.contentMode = .scaleAspectFill avatar.showClearButton = false avatar.showDefault = true avatar.delegate = self avatar.onTap { [weak self] in guard let self else { return } LNBottomSheetMenu.showImageSelectMenu(view: self, options: .init(allowEdit: true)) { [weak self] image, _ in guard let self else { return } guard let image = image?.compress(type: .avatar) else { return } avatar.uploadImage(image: image) } } groupView.container.addSubview(avatar) avatar.snp.makeConstraints { make in make.centerX.equalToSuperview() make.verticalEdges.equalToSuperview() make.width.height.equalTo(105) } return groupView } private func buildNickNameView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "A00188") groupView.descLabel.text = .init(key: "B00038") let container = UIView() container.backgroundColor = .fill_2 container.layer.cornerRadius = 19 groupView.container.addSubview(container) container.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalTo(38) } nameCountLabel.font = .body_m nameCountLabel.textColor = .text_2 container.addSubview(nameCountLabel) nameCountLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } nameInput.font = .body_m nameInput.textColor = .text_5 nameInput.placeholder = .init(key: "B00043") nameInput.delegate = self nameInput.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } nameCountLabel.text = "\(nameInput.text?.count ?? 0)/\(LNProfileManager.nameMaxInput)" }), for: .editingChanged) container.addSubview(nameInput) nameInput.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() make.trailing.equalTo(nameCountLabel.snp.leading).offset(-16) } return groupView } private func buildGenderView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "A00032") groupView.descLabel.text = .init(key: "B00039") let stackView = UIStackView() stackView.spacing = 23 stackView.distribution = .fillEqually groupView.container.addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() } maleButton.setImage(.icGenderMale, for: .normal) maleButton.setTitle(.init(key: "A00014"), for: .normal) maleButton.setTitleColor(.text_5, for: .normal) maleButton.titleLabel?.font = .heading_h4 maleButton.backgroundColor = .primary_1 maleButton.layer.cornerRadius = 20 maleButton.titleEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 0) maleButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } gender = .male checkSaveButton() }), for: .touchUpInside) stackView.addArrangedSubview(maleButton) maleButton.snp.makeConstraints { make in make.height.equalTo(40) } femaleButton.setImage(.icGenderFemale, for: .normal) femaleButton.setTitle(.init(key: "A00015"), for: .normal) femaleButton.setTitleColor(.text_5, for: .normal) femaleButton.titleLabel?.font = .heading_h4 femaleButton.backgroundColor = .primary_1 femaleButton.layer.cornerRadius = 20 femaleButton.titleEdgeInsets = .init(top: 0, left: 8, bottom: 0, right: 0) femaleButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } gender = .female checkSaveButton() }), for: .touchUpInside) stackView.addArrangedSubview(femaleButton) femaleButton.snp.makeConstraints { make in make.height.equalTo(40) } return groupView } private func buildBirthdayView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "A00205") groupView.descLabel.text = .init(key: "B00040") let container = UIView() container.backgroundColor = .fill_2 container.layer.cornerRadius = 19 container.onTap { [weak self] in guard let self else { return } let panel = LNDatePickerPanel() panel.setDefault(birthday) panel.handler = { [weak self] date in guard let self else { return } birthday = date checkSaveButton() } panel.popup() } groupView.container.addSubview(container) container.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalTo(38) } let arrow = UIImageView.arrowImageView(size: 15) arrow.tintColor = .text_3 container.addSubview(arrow) arrow.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } birthdayLabel.font = .heading_h4 birthdayLabel.textColor = .text_5 container.addSubview(birthdayLabel) birthdayLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() } return groupView } private func buildLanguageView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "A00256") groupView.descLabel.isHidden = true let container = UIView() container.backgroundColor = .fill_2 container.layer.cornerRadius = 19 container.onTap { [weak self] in guard let self else { return } let panel = LNLanguageSelectPanel() panel.update(curSelect: curLanguages) panel.handler = { [weak self] languages in guard let self else { return } curLanguages = languages checkSaveButton() } panel.popup() } groupView.container.addSubview(container) container.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalTo(38) } let arrow = UIImageView.arrowImageView(size: 15) arrow.tintColor = .text_3 container.addSubview(arrow) arrow.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } languageLabel.font = .heading_h4 languageLabel.textColor = .text_5 container.addSubview(languageLabel) languageLabel.snp.makeConstraints { make in make.leading.equalToSuperview().offset(16) make.centerY.equalToSuperview() } return groupView } private func buildBioView() -> UIView { let groupView = LNJoinUsInputFieldGroupView() groupView.titleLabel.text = .init(key: "B00042") groupView.descLabel.text = .init(key: "B00041") bioInput.placeholderLabel.text = .init(key: "B00044") bioInput.maxInput = LNProfileManager.bioMaxInput bioInput.delegate = self groupView.container.addSubview(bioInput) bioInput.snp.makeConstraints { make in make.edges.equalToSuperview() make.height.equalTo(130) } return groupView } } #if DEBUG import SwiftUI struct LNJoinUsInputInfoViewPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .fill let view = LNJoinUsInputInfoView() container.addSubview(view) view.snp.makeConstraints { make in make.edges.equalToSuperview() } return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNJoinUsInputInfoViewPreview() }) #endif