// // LNLoginPhoneInputViewController.swift // Gami // // Created by OneeChan on 2026/1/15. // import Foundation import UIKit import SnapKit extension UIView { func pushToLoginPhone() { let vc = LNLoginPhoneInputViewController() navigationController?.pushViewController(vc, animated: true) } } class LNLoginPhoneInputViewController: LNViewController { private let container = UIView() private let countryIcon = UIImageView() private let countryCodeLabel = UILabel() private let phoneInputView = LNTextField() private let confirmButton = UIButton() private var sections: [(code: String, list: [LNCountryCodeVO])] = [] override func viewDidLoad() { super.viewDidLoad() loadCountryCodeList() setupViews() LNEventDeliver.addObserver(self) } } extension LNLoginPhoneInputViewController { private func loadCountryCodeList() { LNConfigManager.shared.getCountryCodeList { [weak self] list in guard let self else { return } guard let list else { return } sortList(list) } } private func sortList(_ list: [LNCountryCodeVO]) { sections.removeAll() var map: [String: [LNCountryCodeVO]] = [:] for item in list { let first = item.name.classificationFirstLetter var section = map[first] ?? [] section.append(item) map[first] = section } let keys = map.keys.sorted() for key in keys { sections.append((key, map[key]) as! (code: String, list: [LNCountryCodeVO])) } var item: LNCountryCodeVO? = list.first { $0.code == LNAppConfig.shared.curLang.countryCode } if item == nil { item = list.first { $0.code == LNAppLanguage.indonesian.countryCode } } if let item { countryIcon.sd_setImage(with: URL(string: item.icon)) countryCodeLabel.text = item.num } } } extension LNLoginPhoneInputViewController: LNAccountManagerNotify { func onLoginCaptchaCoolDownChanged(time: Int) { let text: String = if time == 0 { .init(key: "B00023") } else { .init(key: "B00023") + " (\(time)s)" } confirmButton.setTitle(text, for: .normal) checkConfirmButton() } } extension LNLoginPhoneInputViewController { private func checkConfirmButton() { let text = phoneInputView.text ?? "" confirmButton.isEnabled = !text.isEmpty && LNAccountManager.shared.canSendCaptcha } private func setupViews() { navigationBarColor = .clear let cover = UIImageView(image: .icLoginPhoneBg) view.addSubview(cover) cover.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(fakeNaviBgView) } let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineHeightMultiple = 0.8 let titleLabel = UILabel() titleLabel.attributedText = .init(string: .init(key: "B00020"), attributes: [ .paragraphStyle: paragraphStyle, ]) titleLabel.font = .heading_h1 titleLabel.textColor = .text_5 titleLabel.numberOfLines = 0 titleLabel.clipsToBounds = false titleLabel.layer.masksToBounds = false view.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(24) make.bottom.equalTo(cover.snp.bottom).offset(-42) } container.backgroundColor = .fill container.layer.cornerRadius = 20 container.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.addSubview(container) container.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.top.equalTo(titleLabel.snp.bottom).offset(12) make.bottom.equalToSuperview() } let phoneInput = buildPhoneInput() container.addSubview(phoneInput) phoneInput.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(22) make.top.equalToSuperview().offset(26) } let confirm = buildConfirmButton() container.addSubview(confirm) confirm.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(22) make.top.equalTo(phoneInput.snp.bottom).offset(24) } let loginMethods = buildOtherLoginMethod() container.addSubview(loginMethods) loginMethods.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview() make.bottom.equalToSuperview().offset(view.commonBottomInset) } view.onTap { [weak self] in guard let self else { return } view.endEditing(true) } } private func buildPhoneInput() -> UIView { let container = UIView() container.backgroundColor = .fill_2 container.layer.cornerRadius = 26 container.snp.makeConstraints { make in make.height.equalTo(52) } let countryView = UIView() countryView.onTap { [weak self] in guard let self else { return } guard !sections.isEmpty else { return } let panel = LNCountrySelectPanel() panel.containerHeight = .height(self.container.bounds.height) panel.update(sections) panel.handler = { [weak self] item in guard let self else { return } countryIcon.sd_setImage(with: URL(string: item.icon)) countryCodeLabel.text = item.num } panel.popup() } container.addSubview(countryView) countryView.snp.makeConstraints { make in make.verticalEdges.equalToSuperview() make.leading.equalToSuperview() } countryIcon.layer.cornerRadius = 12 countryIcon.clipsToBounds = true countryView.addSubview(countryIcon) countryIcon.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalToSuperview().offset(16) make.width.height.equalTo(24) } countryCodeLabel.font = .heading_h2 countryCodeLabel.textColor = .text_5 countryCodeLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) countryCodeLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) countryView.addSubview(countryCodeLabel) countryCodeLabel.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(countryIcon.snp.trailing).offset(4) } let config = UIImage.SymbolConfiguration(pointSize: 10) let arrow = UIImageView() arrow.image = .init(systemName: "chevron.down", withConfiguration: config)?.withRenderingMode(.alwaysTemplate) arrow.tintColor = .text_5 countryView.addSubview(arrow) arrow.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(countryCodeLabel.snp.trailing).offset(4) make.trailing.equalToSuperview() make.width.equalTo(12) } phoneInputView.font = .heading_h2 phoneInputView.textColor = .text_5 phoneInputView.attributedPlaceholder = .init(string: .init(key: "B00021"), attributes: [ .font: UIFont.body_l, .foregroundColor: UIColor.text_2 ]) phoneInputView.clearButtonMode = .always phoneInputView.keyboardType = .numberPad phoneInputView.cursorWidth = 2 phoneInputView.cursorHeight = 18 phoneInputView.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } checkConfirmButton() }), for: .editingChanged) container.addSubview(phoneInputView) phoneInputView.snp.makeConstraints { make in make.centerY.equalToSuperview() make.leading.equalTo(countryView.snp.trailing).offset(12) make.trailing.equalToSuperview().offset(-12) } return container } private func buildConfirmButton() -> UIView { confirmButton.setBackgroundImage(.primary_8, for: .normal) confirmButton.layer.cornerRadius = 23.5 confirmButton.clipsToBounds = true confirmButton.setTitle(.init(key: "B00023"), for: .normal) confirmButton.setTitleColor(.text_1, for: .normal) confirmButton.titleLabel?.font = .heading_h3 confirmButton.isEnabled = false confirmButton.addAction(UIAction(handler: { [weak self] _ in guard let self else { return } let code = countryCodeLabel.text let phone = phoneInputView.text guard let code, let phone else { return } showLoading() LNAccountManager.shared.getLoginCaptcha(code: code, phone: phone) { [weak self] success in dismissLoading() guard let self else { return } guard success else { return } view.pushToLoginCaptcha(code: code, phone: phone) } }), for: .touchUpInside) confirmButton.snp.makeConstraints { make in make.height.equalTo(47) } return confirmButton } private func buildOtherLoginMethod() -> UIView { let container = UIView() let titleLabel = UILabel() titleLabel.text = .init(key: "B00099") titleLabel.font = .body_m titleLabel.textColor = .text_3 titleLabel.textAlignment = .center container.addSubview(titleLabel) titleLabel.snp.makeConstraints { make in make.horizontalEdges.equalToSuperview().inset(24) make.top.equalToSuperview() } let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 62 container.addSubview(stackView) stackView.snp.makeConstraints { make in make.centerX.equalToSuperview() make.leading.greaterThanOrEqualToSuperview() make.top.equalTo(titleLabel.snp.bottom).offset(20) make.bottom.equalToSuperview() } let google = buildLoginMethod(icon: .icGoogle) google.onTap { [weak self] in guard let self else { return } LNAccountManager.shared.doGoogleLogin(self) } stackView.addArrangedSubview(google) let apple = buildLoginMethod(icon: .icApple.withTintColor(.init(hex: "#0B0B0A"), renderingMode: .alwaysOriginal)) apple.onTap { [weak self] in guard self != nil else { return } LNAccountManager.shared.doAppleLogin() } stackView.addArrangedSubview(apple) return container } private func buildLoginMethod(icon: UIImage) -> UIView { let container = UIView() container.backgroundColor = .fill_2 container.layer.cornerRadius = 20 container.snp.makeConstraints { make in make.width.height.equalTo(40) } let imageView = UIImageView() imageView.image = icon container.addSubview(imageView) imageView.snp.makeConstraints { make in make.center.equalToSuperview() } return container } } #if DEBUG import SwiftUI struct LNLoginPhoneInputViewControllerPreview: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> some UIViewController { LNLoginPhoneInputViewController() } func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) { } } #Preview(body: { LNLoginPhoneInputViewControllerPreview() }) #endif