225 lines
7.7 KiB
Swift
225 lines
7.7 KiB
Swift
//
|
||
// LoginViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 登录页,UI 严格对齐 Android `LoginScreen`。
|
||
final class LoginViewController: BaseViewController {
|
||
|
||
private let viewModel = LoginViewModel()
|
||
|
||
private let backgroundImageView = UIImageView()
|
||
private let welcomeLabel = UILabel()
|
||
private let formCardView = UIView()
|
||
private let topFormStack = UIStackView()
|
||
private let accountField = LoginTextField(placeholder: "请输入账号")
|
||
private let passwordField = PasswordInputField()
|
||
private let agreementRow = AgreementRowView()
|
||
private let loginButton = AppButton(title: "登录", style: .primary)
|
||
private let loginByCodeButton = AppButton(title: "验证码登录", style: .secondary)
|
||
private let registerContainer = UIStackView()
|
||
private let registerPrefixLabel = UILabel()
|
||
private let registerButton = UIButton(type: .system)
|
||
|
||
private var hasPlayedEntranceAnimation = false
|
||
|
||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||
.lightContent
|
||
}
|
||
|
||
override func setupNavigationBar() {
|
||
navigationController?.setNavigationBarHidden(true, animated: false)
|
||
}
|
||
|
||
override func setupUI() {
|
||
view.backgroundColor = .black
|
||
|
||
backgroundImageView.image = UIImage(named: "img_login_bg")
|
||
backgroundImageView.contentMode = .scaleAspectFill
|
||
backgroundImageView.clipsToBounds = true
|
||
|
||
welcomeLabel.text = "欢迎使用\n随心瞰商家版"
|
||
welcomeLabel.numberOfLines = 2
|
||
welcomeLabel.font = .systemFont(ofSize: 28, weight: .semibold)
|
||
welcomeLabel.textColor = .white
|
||
|
||
formCardView.backgroundColor = .white
|
||
formCardView.layer.cornerRadius = 16
|
||
formCardView.clipsToBounds = true
|
||
|
||
topFormStack.axis = .vertical
|
||
topFormStack.spacing = 0
|
||
topFormStack.alignment = .fill
|
||
|
||
loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
|
||
loginByCodeButton.addTarget(self, action: #selector(loginByCodeButtonTapped), for: .touchUpInside)
|
||
|
||
registerContainer.axis = .horizontal
|
||
registerContainer.alignment = .center
|
||
registerContainer.spacing = 0
|
||
|
||
registerPrefixLabel.text = "没有账号?"
|
||
registerPrefixLabel.font = .systemFont(ofSize: 14, weight: .medium)
|
||
registerPrefixLabel.textColor = AppColor.text333
|
||
|
||
registerButton.setTitle("去注册", for: .normal)
|
||
registerButton.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
|
||
registerButton.setTitleColor(AppColor.primary, for: .normal)
|
||
registerButton.addTarget(self, action: #selector(registerButtonTapped), for: .touchUpInside)
|
||
|
||
registerContainer.addArrangedSubview(registerPrefixLabel)
|
||
registerContainer.addArrangedSubview(registerButton)
|
||
|
||
view.addSubview(backgroundImageView)
|
||
view.addSubview(welcomeLabel)
|
||
view.addSubview(formCardView)
|
||
formCardView.addSubview(topFormStack)
|
||
formCardView.addSubview(registerContainer)
|
||
|
||
topFormStack.addArrangedSubview(accountField)
|
||
topFormStack.setCustomSpacing(8, after: accountField)
|
||
topFormStack.addArrangedSubview(passwordField)
|
||
topFormStack.setCustomSpacing(24, after: passwordField)
|
||
topFormStack.addArrangedSubview(agreementRow)
|
||
topFormStack.setCustomSpacing(16, after: agreementRow)
|
||
topFormStack.addArrangedSubview(loginButton)
|
||
topFormStack.setCustomSpacing(8, after: loginButton)
|
||
topFormStack.addArrangedSubview(loginByCodeButton)
|
||
|
||
bindViewModel()
|
||
updateUI()
|
||
}
|
||
|
||
override func setupConstraints() {
|
||
backgroundImageView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
|
||
welcomeLabel.snp.makeConstraints { make in
|
||
make.top.equalTo(view.safeAreaLayoutGuide).offset(48)
|
||
make.leading.trailing.equalToSuperview().inset(24)
|
||
}
|
||
|
||
formCardView.snp.makeConstraints { make in
|
||
make.top.equalTo(welcomeLabel.snp.bottom).offset(48)
|
||
make.leading.trailing.equalToSuperview().inset(24)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-24)
|
||
}
|
||
|
||
topFormStack.snp.makeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview().inset(16)
|
||
make.bottom.equalToSuperview().inset(16)
|
||
}
|
||
|
||
registerContainer.snp.makeConstraints { make in
|
||
make.centerX.equalToSuperview()
|
||
make.bottom.equalToSuperview().inset(16)
|
||
}
|
||
}
|
||
|
||
override func bindActions() {
|
||
accountField.onTextChange = { [weak self] text in
|
||
self?.viewModel.updateAccount(text)
|
||
}
|
||
accountField.onReturnKey = { [weak self] in
|
||
self?.passwordField.becomeFirstResponder()
|
||
}
|
||
|
||
passwordField.onTextChange = { [weak self] text in
|
||
self?.viewModel.updatePassword(text)
|
||
}
|
||
passwordField.onReturnKey = { [weak self] in
|
||
self?.view.endEditing(true)
|
||
self?.viewModel.login()
|
||
}
|
||
|
||
agreementRow.onCheckedChange = { [weak self] in
|
||
self?.viewModel.togglePrivacyChecked()
|
||
}
|
||
agreementRow.onUserAgreementTap = { [weak self] in
|
||
self?.viewModel.onUserAgreement?()
|
||
}
|
||
agreementRow.onPrivacyPolicyTap = { [weak self] in
|
||
self?.viewModel.onPrivacyPolicy?()
|
||
}
|
||
}
|
||
|
||
override func viewDidAppear(_ animated: Bool) {
|
||
super.viewDidAppear(animated)
|
||
playEntranceAnimationIfNeeded()
|
||
}
|
||
|
||
private func bindViewModel() {
|
||
viewModel.onStateChange = { [weak self] in
|
||
self?.updateUI()
|
||
}
|
||
viewModel.onShowMessage = { [weak self] message in
|
||
self?.showToast(message)
|
||
}
|
||
viewModel.onLogin = { [weak self] in
|
||
self?.showToast("登录功能待接入")
|
||
}
|
||
viewModel.onLoginByCode = { [weak self] in
|
||
self?.showToast("验证码登录待接入")
|
||
}
|
||
viewModel.onRegister = { [weak self] in
|
||
self?.showToast("注册待接入")
|
||
}
|
||
viewModel.onUserAgreement = { [weak self] in
|
||
self?.showToast("用户协议待接入")
|
||
}
|
||
viewModel.onPrivacyPolicy = { [weak self] in
|
||
self?.showToast("隐私政策待接入")
|
||
}
|
||
}
|
||
|
||
private func updateUI() {
|
||
agreementRow.isChecked = viewModel.isPrivacyChecked
|
||
loginButton.isEnabled = viewModel.isLoginEnabled
|
||
|
||
let showRegisterSection = viewModel.enableRegister
|
||
loginByCodeButton.isHidden = !showRegisterSection
|
||
registerContainer.isHidden = !showRegisterSection
|
||
|
||
topFormStack.snp.remakeConstraints { make in
|
||
make.top.leading.trailing.equalToSuperview().inset(16)
|
||
if showRegisterSection {
|
||
make.bottom.lessThanOrEqualTo(registerContainer.snp.top).offset(-16)
|
||
} else {
|
||
make.bottom.equalToSuperview().inset(16)
|
||
}
|
||
}
|
||
}
|
||
|
||
private func playEntranceAnimationIfNeeded() {
|
||
guard !hasPlayedEntranceAnimation else { return }
|
||
hasPlayedEntranceAnimation = true
|
||
|
||
formCardView.transform = CGAffineTransform(translationX: 0, y: view.bounds.height)
|
||
welcomeLabel.alpha = 0
|
||
|
||
UIView.animate(withDuration: 0.4, delay: 0, options: .curveEaseOut) {
|
||
self.formCardView.transform = .identity
|
||
self.welcomeLabel.alpha = 1
|
||
}
|
||
}
|
||
|
||
@objc private func loginButtonTapped() {
|
||
view.endEditing(true)
|
||
viewModel.login()
|
||
}
|
||
|
||
@objc private func loginByCodeButtonTapped() {
|
||
view.endEditing(true)
|
||
viewModel.onLoginByCode?()
|
||
}
|
||
|
||
@objc private func registerButtonTapped() {
|
||
view.endEditing(true)
|
||
viewModel.onRegister?()
|
||
}
|
||
}
|