// // LNBaseInfoSetupViewController.swift // Lanu // // Created by OneeChan on 2025/12/2. // import Foundation import UIKit import SnapKit import Combine extension UIView { func pushToBaseInfoSetup(_ config: LNProfileUpdateConfig) { let vc = LNBaseInfoSetupViewController(config: config) navigationController?.pushViewController(vc, animated: true) } } enum LNAvatarModifyType: CaseIterable { case camera case photo case random var title: String { switch self { case .camera: .init(key: "A00011") case .photo: .init(key: "A00012") case .random: .init(key: "A00100") } } } class LNBaseInfoSetupViewController: LNViewController { private let updateConfig: LNProfileUpdateConfig private let container = UIView() private let avatar = LNImageUploadView() private let nameInputField = UITextField() private let randomView = UIView() // private let birthDayLabel = UILabel() // private var curDate: TimeInterval = Date().yearAgo(-18).timeIntervalSince1970 { // didSet { // birthDayLabel.text = curDate.formattedFullDate("-") // } // } private let nextButton = UIButton() private var randomProfile: LNProfileRandomInfoVO? init(config: LNProfileUpdateConfig) { updateConfig = config super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupViews() loadRandomProfile() LNEventDeliver.addObserver(self) } } extension LNBaseInfoSetupViewController { private func changeAvatar() { let handler: (UIImage?, URL?) -> Void = { [weak self] image, _ in guard let self, let image = image?.compress(type: .avatar) else { return } avatar.uploadImage(image: image) checkNextButtonEnable() } let panel = LNBottomSheetMenu() var titles: [String] = [ LNAvatarModifyType.camera.title, LNAvatarModifyType.photo.title, ] if randomProfile?.avatars.isEmpty == false { titles.append(LNAvatarModifyType.random.title) } panel.update(titles) { [weak self] index, text in guard let self else { return } if index == 0 { LNMediaPicker.shared.pick(from: view, type: .camera, options: .init(allowEdit: true), handler: handler) } else if index == 1 { LNMediaPicker.shared.pick(from: view, type: .photo, options: .init(allowEdit: true), handler: handler) } else if index == 2 { guard let item = randomProfile?.avatars.randomElement() else { return } avatar.loadImage(url: item) checkNextButtonEnable() } } panel.popup() } private func loadRandomProfile() { LNProfileManager.shared.getRandomProfile { [weak self] male, female in guard let self else { return } if updateConfig.gender == .male, let male { randomProfile = male randomView.isHidden = male.nicknames.isEmpty } else if updateConfig.gender == .female, let female { randomProfile = female randomView.isHidden = female.nicknames.isEmpty } if let url = randomProfile?.avatars.randomElement() { avatar.loadImage(url: url) } nameInputField.text = randomProfile?.nicknames.randomElement() checkNextButtonEnable() } } } extension LNBaseInfoSetupViewController: LNImageUploadViewDelegate { func onImageUploadViewStartUpload(view: LNImageUploadView) { checkNextButtonEnable() } func onImageUploadView(view: LNImageUploadView, didUploadImage url: String) { checkNextButtonEnable() } } extension LNBaseInfoSetupViewController: LNKeyboardNotify { func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) { guard let curInput, curInput.isDescendant(of: view) else { return } let frame = curInput.convert(curInput.frame, to: view) let offset = keyboardHeight - (view.bounds.height - CGRectGetMaxY(frame) - 20) if offset > 0 { container.snp.updateConstraints { make in make.top.equalToSuperview().offset(-offset) } } } func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) { guard curInput == nameInputField else { return } view.layoutIfNeeded() } func onKeyboardWillHide(curInput: UIView?) { guard curInput == nameInputField else { return } container.snp.updateConstraints { make in make.top.equalToSuperview().offset(0) } } func onKeyboardHide(curInput: UIView?) { guard curInput == nameInputField else { return } view.layoutIfNeeded() } } extension LNBaseInfoSetupViewController { private func checkNextButtonEnable() { let name = nameInputField.text ?? "" nextButton.isEnabled = !name.isEmpty && avatar.imageUrl?.isEmpty == false } private func setupViews() { navigationBarColor = .clear view.backgroundColor = .primary_1 view.addSubview(container) container.onTap { [weak self] in guard let self else { return } self.view.endEditing(true) } container.snp.makeConstraints { make in make.top.equalToSuperview().offset(0) make.horizontalEdges.equalToSuperview() make.height.equalToSuperview() } let bg = UIImageView() bg.image = .icLoginProfileBg container.addSubview(bg) bg.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(fakeNaviBgView) } let titleView = buildTitles() container.addSubview(titleView) titleView.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(22) make.top.equalToSuperview().offset(22) } let baseInfo = buildBaseInfoView() container.addSubview(baseInfo) baseInfo.snp.makeConstraints { make in make.centerY.equalToSuperview().multipliedBy(0.8) make.horizontalEdges.equalToSuperview().inset(22) } nextButton.setBackgroundImage(.primary_8, for: .normal) nextButton.setTitle(.init(key: "A00101"), for: .normal) nextButton.setTitleColor(.text_1, for: .normal) nextButton.titleLabel?.font = .heading_h3 nextButton.layer.cornerRadius = 23.5 nextButton.clipsToBounds = true nextButton.isEnabled = false nextButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } updateConfig.nickName = nameInputField.text ?? "" // updateConfig.birthday = curDate.formattedFullDate("-", normal: true) updateConfig.avatar = avatar.imageUrl ?? "" view.pushToInterestSetup(updateConfig) }), for: .touchUpInside) container.addSubview(nextButton) nextButton.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(16) make.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-4) make.height.equalTo(47) } } private func buildTitles() -> UIView { let stackView = UIStackView() stackView.axis = .vertical stackView.spacing = 6 let mainLabel = UILabel() mainLabel.text = .init(key: "A00102") mainLabel.font = .init(name: UIFont.boldFontName, size: 40) mainLabel.textColor = .text_5 mainLabel.numberOfLines = 0 stackView.addArrangedSubview(mainLabel) let subTitleLabel = UILabel() subTitleLabel.text = .init(key: "A00103") subTitleLabel.font = .heading_h1 subTitleLabel.numberOfLines = 0 stackView.addArrangedSubview(subTitleLabel) return stackView } private func buildBaseInfoView() -> UIView { let container = UIView() let avatar = buildAvatar() container.addSubview(avatar) avatar.snp.makeConstraints { make in make.centerX.equalToSuperview() make.top.equalToSuperview() } let name = buildNickName() container.addSubview(name) name.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(avatar.snp.bottom).offset(16) make.bottom.equalToSuperview() } // let birthDay = buildBirthDay() // container.addSubview(birthDay) // birthDay.snp.makeConstraints { make in // make.horizontalEdges.equalToSuperview() // make.top.equalTo(name.snp.bottom).offset(22) // make.bottom.equalToSuperview() // } return container } private func buildAvatar() -> UIView { let container = UIView() avatar.uploadType = .avatar avatar.image = .icProfileLoginAvatar avatar.layer.borderColor = UIColor.fill.cgColor avatar.layer.borderWidth = 2 avatar.layer.cornerRadius = 60 avatar.clipsToBounds = true avatar.delegate = self avatar.onTap { [weak self] in guard let self else { return } changeAvatar() } container.addSubview(avatar) avatar.snp.makeConstraints { make in make.width.height.equalTo(120) make.edges.equalToSuperview() } let editAvatar = UIButton() editAvatar.setImage(.icLogonAvatarEdit, for: .normal) editAvatar.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } changeAvatar() }), for: .touchUpInside) container.addSubview(editAvatar) editAvatar.snp.makeConstraints { make in make.trailing.bottom.equalToSuperview() } return container } private func buildNickName() -> UIView { let container = UIView() container.backgroundColor = .fill container.layer.cornerRadius = 26 container.snp.makeConstraints { make in make.height.equalTo(52) } randomView.isHidden = true container.addSubview(randomView) randomView.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.trailing.equalToSuperview().offset(-16) } let config = UIImage.SymbolConfiguration(pointSize: 16) let refresh = UIButton() refresh.setImage(.init(systemName: "arrow.clockwise", withConfiguration: config), for: .normal) refresh.tintColor = .text_3 refresh.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } guard let randomProfile else { return } nameInputField.text = randomProfile.nicknames.randomElement() checkNextButtonEnable() }), for: .touchUpInside) randomView.addSubview(refresh) refresh.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview() make.width.equalTo(17) } let random = UILabel() random.text = .init(key: "A00104") random.font = .body_m random.textColor = .text_3 random.setContentHuggingPriority(.defaultHigh, for: .horizontal) random.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) randomView.addSubview(random) random.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalTo(refresh.snp.leading).offset(-6) make.leading.equalToSuperview() } nameInputField.textColor = .text_5 nameInputField.font = .heading_h3 nameInputField.attributedPlaceholder = .init( string: .init(key: "A00105"), attributes: [ .font: UIFont.body_l, .foregroundColor: UIColor.text_2 ] ) nameInputField.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } checkNextButtonEnable() }), for: .editingChanged) container.addSubview(nameInputField) nameInputField.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.leading.equalToSuperview().offset(16) make.trailing.equalTo(randomView.snp.leading).offset(-6) } return container } // private func buildBirthDay() -> UIView { // let container = UIView() // container.backgroundColor = .fill // container.layer.cornerRadius = 26 // container.snp.makeConstraints { make in // make.height.equalTo(52) // } // container.onTap { [weak self] in // guard let self else { return } // let panel = LNBirthdayDatePickerPanel() // panel.setDefault(curDate) // panel.handler = { [weak self] date in // guard let self else { return } // curDate = date // } // panel.showIn(view) // } // // let config = UIImage.SymbolConfiguration(pointSize: 6) // let arrow = UIImageView() // arrow.image = .init(systemName: "arrowtriangle.down.fill", withConfiguration: config) // arrow.tintColor = .text_5 // container.addSubview(arrow) // arrow.snp.makeConstraints { make in // make.centerY.equalToSuperview() // make.trailing.equalToSuperview().offset(-16) // } // // birthDayLabel.text = curDate.formattedFullDate("-") // birthDayLabel.font = .body_l // birthDayLabel.textColor = .text_5 // container.addSubview(birthDayLabel) // birthDayLabel.snp.makeConstraints { make in // make.centerY.equalToSuperview() // make.leading.equalToSuperview().offset(16) // } // // return container // } } #if DEBUG import SwiftUI struct LNBaseInfoSetupViewControllerPreview: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { LNBaseInfoSetupViewController(config: LNProfileUpdateConfig()) } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } #Preview(body: { LNBaseInfoSetupViewControllerPreview() }) #endif