Files
suixinkan_uikit/suixinkan/UI/Login/AccountSelectionViewController.swift
T

340 lines
12 KiB
Swift

//
// AccountSelectionViewController.swift
// suixinkan
//
import SnapKit
import UIKit
private typealias AccountSelectionSection = Int
private typealias AccountSelectionItem = String
/// 多账号登录时的账号选择页,展示景区/门店账号列表并提交选择。
final class AccountSelectionViewController: UIViewController, UITableViewDelegate {
private let payload: AccountSelectionPayload
private var isLoading: Bool
private let onCancel: () -> Void
private let onConfirm: (AccountSwitchAccount) -> Void
private var selectedAccountId: String?
private let tableView = UITableView(frame: .zero, style: .plain)
private var tableDataSource: UITableViewDiffableDataSource<AccountSelectionSection, AccountSelectionItem>!
private let confirmButton = UIButton(type: .system)
private let bottomBar = UIView()
init(
payload: AccountSelectionPayload,
isLoading: Bool,
onCancel: @escaping () -> Void,
onConfirm: @escaping (AccountSwitchAccount) -> Void
) {
self.payload = payload
self.isLoading = isLoading
self.onCancel = onCancel
self.onConfirm = onConfirm
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
title = "选择账号"
view.backgroundColor = UIColor(hex: 0xF5F7FB)
navigationItem.leftBarButtonItem = UIBarButtonItem(
title: "取消",
style: .plain,
target: self,
action: #selector(cancelTapped)
)
isModalInPresentation = isLoading
selectedAccountId = payload.accounts.first?.id
configureTableView()
configureTableDataSource()
configureBottomBar()
}
/// 更新 Loading 状态。
func updateLoading(_ loading: Bool) {
isLoading = loading
isModalInPresentation = loading
confirmButton.isEnabled = canConfirm
confirmButton.backgroundColor = canConfirm ? AppColor.primary : AppColor.buttonDisabled
}
private var selectedAccount: AccountSwitchAccount? {
payload.accounts.first { $0.id == selectedAccountId }
}
private var canConfirm: Bool {
selectedAccount != nil && !isLoading
}
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<AccountSelectionSection, AccountSelectionItem>(
tableView: tableView
) { [weak self] tableView, indexPath, item in
guard let self,
let cell = tableView.dequeueReusableCell(
withIdentifier: AccountSelectionCell.reuseIdentifier,
for: indexPath
) as? AccountSelectionCell,
let account = self.payload.accounts.first(where: { $0.id == item }) else {
return UITableViewCell()
}
cell.configure(account: account, selected: account.id == self.selectedAccountId)
return cell
}
applyTableSnapshot(animated: false)
}
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<AccountSelectionSection, AccountSelectionItem> {
var snapshot = NSDiffableDataSourceSnapshot<AccountSelectionSection, AccountSelectionItem>()
snapshot.appendSections([0])
snapshot.appendItems(payload.accounts.map(\.id), toSection: 0)
return snapshot
}
private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) {
var snapshot = buildTableSnapshot()
if reconfigure, !snapshot.itemIdentifiers.isEmpty {
snapshot.reconfigureItems(snapshot.itemIdentifiers)
}
tableDataSource.apply(snapshot, animatingDifferences: animated)
}
private func configureTableView() {
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.contentInset = UIEdgeInsets(top: 10, left: 0, bottom: 10, right: 0)
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 92
tableView.delegate = self
tableView.register(AccountSelectionCell.self, forCellReuseIdentifier: AccountSelectionCell.reuseIdentifier)
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(90)
}
}
private func configureBottomBar() {
bottomBar.backgroundColor = .white
let divider = UIView()
divider.backgroundColor = UIColor.separator
confirmButton.setTitle("进入系统", for: .normal)
confirmButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
confirmButton.setTitleColor(.white, for: .normal)
confirmButton.layer.cornerRadius = 8
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
updateLoading(isLoading)
view.addSubview(bottomBar)
bottomBar.addSubview(divider)
bottomBar.addSubview(confirmButton)
bottomBar.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
}
divider.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.height.equalTo(0.5)
}
confirmButton.snp.makeConstraints { make in
make.top.equalTo(divider.snp.bottom).offset(12)
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(14)
make.height.equalTo(50)
}
}
@objc private func cancelTapped() {
onCancel()
dismiss(animated: true)
}
@objc private func confirmTapped() {
guard canConfirm, let selectedAccount else { return }
onConfirm(selectedAccount)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let item = tableDataSource.itemIdentifier(for: indexPath) else { return }
selectedAccountId = item
applyTableSnapshot(animated: false, reconfigure: true)
confirmButton.isEnabled = canConfirm
confirmButton.backgroundColor = canConfirm ? AppColor.primary : AppColor.buttonDisabled
}
}
/// 账号选择列表 Cell。
private final class AccountSelectionCell: UITableViewCell {
static let reuseIdentifier = "AccountSelectionCell"
private let card = UIView()
private let avatarView = UIImageView()
private let avatarPlaceholderLabel = UILabel()
private let titleLabel = UILabel()
private let subtitleLabel = UILabel()
private let nameLabel = UILabel()
private let typeTag = UILabel()
private let currentTag = UILabel()
private let checkmark = UIImageView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
card.backgroundColor = .white
card.layer.cornerRadius = 10
card.layer.masksToBounds = true
avatarView.clipsToBounds = true
avatarView.layer.cornerRadius = 25
avatarView.contentMode = .scaleAspectFill
avatarPlaceholderLabel.font = .systemFont(ofSize: 14, weight: .semibold)
avatarPlaceholderLabel.textAlignment = .center
titleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
titleLabel.textColor = AppColor.text333
subtitleLabel.font = .systemFont(ofSize: 13)
subtitleLabel.textColor = AppColor.text666
nameLabel.font = .systemFont(ofSize: 12)
nameLabel.textColor = UIColor(hex: 0x9AA1AA)
typeTag.font = .systemFont(ofSize: 11, weight: .semibold)
typeTag.textAlignment = .center
typeTag.layer.cornerRadius = 4
typeTag.clipsToBounds = true
typeTag.setContentCompressionResistancePriority(.required, for: .horizontal)
currentTag.font = .systemFont(ofSize: 11, weight: .semibold)
currentTag.textAlignment = .center
currentTag.text = "当前"
currentTag.textColor = AppColor.primary
currentTag.backgroundColor = AppColor.secondaryButtonBackground
currentTag.layer.cornerRadius = 4
currentTag.clipsToBounds = true
currentTag.isHidden = true
checkmark.contentMode = .scaleAspectFit
contentView.addSubview(card)
card.addSubview(avatarView)
avatarView.addSubview(avatarPlaceholderLabel)
card.addSubview(titleLabel)
card.addSubview(subtitleLabel)
card.addSubview(nameLabel)
card.addSubview(typeTag)
card.addSubview(currentTag)
card.addSubview(checkmark)
card.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: 6, left: 16, bottom: 6, right: 16))
}
avatarView.snp.makeConstraints { make in
make.leading.equalToSuperview().inset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(50)
}
avatarPlaceholderLabel.snp.makeConstraints { make in
make.center.equalToSuperview()
}
titleLabel.snp.makeConstraints { make in
make.top.equalToSuperview().inset(16)
make.leading.equalTo(avatarView.snp.trailing).offset(12)
make.trailing.lessThanOrEqualTo(typeTag.snp.leading).offset(-8)
}
currentTag.snp.makeConstraints { make in
make.leading.equalTo(titleLabel.snp.trailing).offset(6)
make.trailing.lessThanOrEqualTo(typeTag.snp.leading).offset(-8)
make.centerY.equalTo(titleLabel)
make.height.equalTo(18)
}
subtitleLabel.snp.makeConstraints { make in
make.top.equalTo(titleLabel.snp.bottom).offset(4)
make.leading.equalTo(titleLabel)
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
}
nameLabel.snp.makeConstraints { make in
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
make.leading.equalTo(titleLabel)
make.bottom.equalToSuperview().inset(16)
}
typeTag.snp.makeConstraints { make in
make.trailing.equalTo(checkmark.snp.leading).offset(-8)
make.top.equalTo(titleLabel)
make.height.equalTo(18)
}
checkmark.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(16)
make.centerY.equalToSuperview()
make.width.height.equalTo(22)
}
}
required init?(coder: NSCoder) {
nil
}
func configure(account: AccountSwitchAccount, selected: Bool) {
titleLabel.text = account.title.isEmpty ? account.accountTypeLabel : account.title
subtitleLabel.text = account.subtitle
subtitleLabel.isHidden = account.subtitle.isEmpty
nameLabel.text = account.realName
nameLabel.isHidden = account.realName.isEmpty
currentTag.isHidden = !account.isCurrent
let avatarPlaceholderColor: UIColor
let avatarTextColor: UIColor
if account.isStoreUser {
avatarPlaceholderColor = UIColor(hex: 0xE8F8F1)
avatarTextColor = UIColor(hex: 0x0F9F6E)
avatarPlaceholderLabel.text = "店"
typeTag.text = " 门店 "
typeTag.textColor = UIColor(hex: 0x0F9F6E)
typeTag.backgroundColor = UIColor(hex: 0xE8F8F1)
} else {
avatarPlaceholderColor = UIColor(hex: 0xF3ECFF)
avatarTextColor = UIColor(hex: 0x7C3AED)
avatarPlaceholderLabel.text = "景"
typeTag.text = " 景区 "
typeTag.textColor = UIColor(hex: 0x7C3AED)
typeTag.backgroundColor = UIColor(hex: 0xF3ECFF)
}
avatarPlaceholderLabel.textColor = avatarTextColor
avatarPlaceholderLabel.isHidden = !account.avatar.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
avatarView.loadRemoteImage(urlString: account.avatar, placeholderColor: avatarPlaceholderColor)
let symbolName = selected ? "checkmark.circle.fill" : "circle"
checkmark.image = UIImage(systemName: symbolName)
checkmark.tintColor = selected ? AppColor.primary : UIColor(hex: 0xB6BECA)
card.layer.borderWidth = selected ? 1.4 : 1
card.layer.borderColor = (selected ? AppColor.primary : UIColor(hex: 0xE6ECF4)).cgColor
}
}