340 lines
12 KiB
Swift
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 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
|
||
}
|
||
}
|