feat: 添加新的登录模块及相关组件

主要变更:
1. 新增 EPLoginViewController 和 EPLoginTypesViewController,提供新的登录界面和功能。
2. 引入 EPLoginInputView 和 EPLoginButton 组件,支持输入框和按钮的自定义。
3. 实现 EPLoginService 和 EPLoginManager,封装登录逻辑和 API 请求。
4. 添加 EPLoginConfig 和 EPLoginState,统一配置和状态管理。
5. 更新 Bridging Header,确保 Swift 和 Objective-C 代码的互操作性。

此更新旨在提升用户登录体验,简化登录流程,并提供更好的代码结构和可维护性。
This commit is contained in:
edwinQQQ
2025-10-13 15:40:43 +08:00
parent 26d9894830
commit 809cc44ca5
14 changed files with 3438 additions and 1213 deletions

View File

@@ -0,0 +1,252 @@
//
// EPLoginViewController.swift
// YuMi
//
// Created by AI on 2025-01-27.
//
import UIKit
@objc class EPLoginViewController: UIViewController {
// MARK: - Properties
private let backgroundImageView = UIImageView()
private let logoImageView = UIImageView()
private let epartiTitleLabel = UILabel()
private let idLoginButton = EPLoginButton()
private let emailLoginButton = EPLoginButton()
private let agreeCheckbox = UIButton(type: .custom)
private let policyLabel = EPPolicyLabel()
private let feedbackButton = UIButton(type: .custom)
#if DEBUG
private let debugButton = UIButton(type: .custom)
#endif
private let policySelectedKey = EPLoginConfig.Keys.policyAgreed
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
loadPolicyStatus()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// navigationController?.setNavigationBarHidden(false, animated: animated)
}
// MARK: - Setup
private func setupUI() {
setupBackground()
setupLogo()
setupLoginButtons()
setupPolicyArea()
setupNavigationBar()
}
private func setupBackground() {
view.addSubview(backgroundImageView)
backgroundImageView.image = kImage(EPLoginConfig.Images.background)
backgroundImageView.contentMode = .scaleAspectFill
backgroundImageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func setupLogo() {
view.addSubview(logoImageView)
logoImageView.image = kImage(EPLoginConfig.Images.loginBg)
logoImageView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view)
make.height.equalTo(EPLoginConfig.Layout.logoHeight)
}
// E-PARTI
view.addSubview(epartiTitleLabel)
epartiTitleLabel.text = "E-PARTI"
epartiTitleLabel.font = .systemFont(ofSize: EPLoginConfig.Layout.epartiTitleFontSize, weight: .bold)
epartiTitleLabel.textColor = EPLoginConfig.Colors.textLight
epartiTitleLabel.transform = CGAffineTransform(a: 1, b: 0, c: -0.2, d: 1, tx: 0, ty: 0) //
epartiTitleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(EPLoginConfig.Layout.epartiTitleLeading)
make.bottom.equalTo(logoImageView.snp.bottom).offset(EPLoginConfig.Layout.epartiTitleBottomOffset)
}
}
private func setupLoginButtons() {
//
idLoginButton.configure(
icon: EPLoginConfig.Images.iconLoginId,
title: YMLocalizedString(EPLoginConfig.LocalizedKeys.idLogin)
)
idLoginButton.delegate = self
emailLoginButton.configure(
icon: EPLoginConfig.Images.iconLoginEmail,
title: YMLocalizedString(EPLoginConfig.LocalizedKeys.emailLogin)
)
emailLoginButton.delegate = self
// StackView
let stackView = UIStackView(arrangedSubviews: [idLoginButton, emailLoginButton])
stackView.axis = .vertical
stackView.spacing = EPLoginConfig.Layout.loginButtonSpacing
stackView.distribution = .fillEqually
view.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(EPLoginConfig.Layout.loginButtonHorizontalPadding)
make.trailing.equalToSuperview().offset(-EPLoginConfig.Layout.loginButtonHorizontalPadding)
make.top.equalTo(logoImageView.snp.bottom)
}
idLoginButton.snp.makeConstraints { make in
make.height.equalTo(EPLoginConfig.Layout.loginButtonHeight)
}
emailLoginButton.snp.makeConstraints { make in
make.height.equalTo(EPLoginConfig.Layout.loginButtonHeight)
}
}
private func setupPolicyArea() {
view.addSubview(agreeCheckbox)
view.addSubview(policyLabel)
agreeCheckbox.setImage(kImage("login_privace_select"), for: .selected)
agreeCheckbox.setImage(kImage("login_privace_unselect"), for: .normal)
agreeCheckbox.addTarget(self, action: #selector(togglePolicyCheckbox), for: .touchUpInside)
policyLabel.onUserAgreementTapped = { [weak self] in
self?.openPolicy(url: "https://example.com/user-agreement")
}
policyLabel.onPrivacyPolicyTapped = { [weak self] in
self?.openPolicy(url: "https://example.com/privacy-policy")
}
agreeCheckbox.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(EPLoginConfig.Layout.horizontalPadding)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-30)
make.size.equalTo(EPLoginConfig.Layout.checkboxSize)
}
policyLabel.snp.makeConstraints { make in
make.leading.equalTo(agreeCheckbox.snp.trailing).offset(8)
make.trailing.equalToSuperview().offset(-EPLoginConfig.Layout.horizontalPadding)
make.centerY.equalTo(agreeCheckbox)
}
}
private func setupNavigationBar() {
view.addSubview(feedbackButton)
feedbackButton.setTitle(YMLocalizedString(EPLoginConfig.LocalizedKeys.feedback), for: .normal)
feedbackButton.titleLabel?.font = .systemFont(ofSize: EPLoginConfig.Layout.smallFontSize)
feedbackButton.backgroundColor = EPLoginConfig.Colors.backgroundTransparent
feedbackButton.layer.cornerRadius = EPLoginConfig.Layout.feedbackButtonCornerRadius
feedbackButton.addTarget(self, action: #selector(handleFeedback), for: .touchUpInside)
feedbackButton.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-EPLoginConfig.Layout.compactHorizontalPadding)
make.top.equalTo(view.safeAreaLayoutGuide).offset(8)
make.height.equalTo(EPLoginConfig.Layout.feedbackButtonHeight)
}
#if DEBUG
view.addSubview(debugButton)
debugButton.setTitle("切换环境", for: .normal)
debugButton.setTitleColor(.blue, for: .normal)
debugButton.addTarget(self, action: #selector(handleDebug), for: .touchUpInside)
debugButton.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(EPLoginConfig.Layout.compactHorizontalPadding)
make.top.equalTo(view.safeAreaLayoutGuide).offset(8)
}
#endif
}
// MARK: - Actions
private func handleIDLogin() {
let vc = EPLoginTypesViewController()
vc.displayType = .id
navigationController?.pushViewController(vc, animated: true)
}
private func handleEmailLogin() {
let vc = EPLoginTypesViewController()
vc.displayType = .email
navigationController?.pushViewController(vc, animated: true)
}
@objc private func togglePolicyCheckbox() {
agreeCheckbox.isSelected.toggle()
UserDefaults.standard.set(agreeCheckbox.isSelected, forKey: policySelectedKey)
}
@objc private func handleFeedback() {
print("[EPLogin] Feedback - 占位Phase 2 实现")
}
#if DEBUG
@objc private func handleDebug() {
print("[EPLogin] Debug - 占位Phase 2 实现")
}
#endif
private func openPolicy(url: String) {
let webVC = XPWebViewController(roomUID: nil)
webVC.url = url
navigationController?.pushViewController(webVC, animated: true)
}
// MARK: - Helpers
private func loadPolicyStatus() {
agreeCheckbox.isSelected = UserDefaults.standard.bool(forKey: policySelectedKey)
//
if !UserDefaults.standard.bool(forKey: EPLoginConfig.Keys.hasLaunchedBefore) {
agreeCheckbox.isSelected = true
UserDefaults.standard.set(true, forKey: policySelectedKey)
UserDefaults.standard.set(true, forKey: EPLoginConfig.Keys.hasLaunchedBefore)
}
}
private func checkPolicyAgreed() -> Bool {
if !agreeCheckbox.isSelected {
// Phase 2:
print("[EPLogin] Please agree to policy first")
return false
}
return true
}
}
// MARK: - EPLoginButtonDelegate
extension EPLoginViewController: EPLoginButtonDelegate {
func loginButtonDidTap(_ button: EPLoginButton) {
guard checkPolicyAgreed() else { return }
if button == idLoginButton {
handleIDLogin()
} else if button == emailLoginButton {
handleEmailLogin()
}
}
}