Files
suixinkan_uikit/suixinkan/UI/Profile/AccountSwitchViewController.swift

271 lines
11 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AccountSwitchViewController.swift
// suixinkan
//
import SnapKit
import UIKit
private typealias AccountSwitchSection = Int
private typealias AccountSwitchItem = String
///
final class AccountSwitchViewController: BaseViewController, UITableViewDelegate {
private let viewModel = AccountSwitchViewModel()
private let profileAPI = NetworkServices.shared.profileAPI
private let authAPI = NetworkServices.shared.authAPI
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .plain)
table.backgroundColor = UIColor(hex: 0xF5F7FB)
table.separatorStyle = .none
table.delegate = self
table.register(AccountSwitchCell.self, forCellReuseIdentifier: AccountSwitchCell.reuseID)
return table
}()
private let confirmButton = AppButton(title: "确认切换")
private var tableDataSource: UITableViewDiffableDataSource<AccountSwitchSection, AccountSwitchItem>!
override func setupNavigationBar() {
title = "账号切换"
}
override func setupUI() {
view.backgroundColor = UIColor(hex: 0xF5F7FB)
configureTableDataSource()
view.addSubview(tableView)
view.addSubview(confirmButton)
}
override func setupConstraints() {
tableView.snp.makeConstraints { make in
make.top.leading.trailing.equalToSuperview()
make.bottom.equalTo(confirmButton.snp.top).offset(-12)
}
confirmButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(14)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
self?.applyTableSnapshot(reconfigure: true)
self?.updateConfirmButton()
}
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
Task { await loadAccounts() }
}
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource(tableView: tableView) { [weak self] tableView, indexPath, item in
guard let self else { return UITableViewCell() }
let cell = tableView.dequeueReusableCell(withIdentifier: AccountSwitchCell.reuseID, for: indexPath) as! AccountSwitchCell
guard let account = self.viewModel.accounts.first(where: { $0.id == item }) else { return cell }
cell.configure(
account: account,
selected: account.id == self.viewModel.selectedAccountId,
isCurrent: account.isCurrent || self.isCurrentAccount(account)
)
return cell
}
applyTableSnapshot(animated: false)
}
private func buildSnapshot() -> NSDiffableDataSourceSnapshot<AccountSwitchSection, AccountSwitchItem> {
var snapshot = NSDiffableDataSourceSnapshot<AccountSwitchSection, AccountSwitchItem>()
snapshot.appendSections([0])
snapshot.appendItems(viewModel.accounts.map(\.id), toSection: 0)
return snapshot
}
private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) {
var snapshot = buildSnapshot()
if reconfigure, !snapshot.itemIdentifiers.isEmpty {
snapshot.reconfigureItems(snapshot.itemIdentifiers)
}
tableDataSource.apply(snapshot, animatingDifferences: animated)
}
private func updateConfirmButton() {
let enabled = viewModel.selectedAccount != nil && !viewModel.loading && !viewModel.switching
confirmButton.isEnabled = enabled
}
private var currentAccountId: String? {
let store = AppStore.shared
if store.session.currentStoreId > 0 {
return "\(V9StoreUser.accountTypeValue)_\(store.session.currentStoreId)"
}
if store.session.currentScenicId > 0 {
return "\(V9ScenicUser.accountTypeValue)_\(store.session.userId)"
}
return store.session.userId.isEmpty ? nil : store.session.userId
}
private func isCurrentAccount(_ account: AccountSwitchAccount) -> Bool {
viewModel.isCurrent(account, currentAccountId: currentAccountId)
}
private func loadAccounts(force: Bool = false) async {
showLoading()
defer { hideLoading() }
do {
try await viewModel.load(api: profileAPI, force: force, currentAccountId: currentAccountId)
} catch {
showToast(error.localizedDescription)
}
}
@objc private func confirmTapped() {
guard let account = viewModel.selectedAccount else { return }
if account.isCurrent || isCurrentAccount(account) {
navigationController?.popViewController(animated: true)
return
}
Task { await switchAccount(account) }
}
private func switchAccount(_ account: AccountSwitchAccount) async {
showLoading()
defer { hideLoading() }
do {
let response = try await viewModel.switchAccount(account, api: authAPI)
AuthSessionHelper.applyAccountSwitch(with: response, account: account)
showToast("账号已切换")
navigationController?.popViewController(animated: true)
} catch {
showToast(error.localizedDescription)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard let item = tableDataSource.itemIdentifier(for: indexPath),
let account = viewModel.accounts.first(where: { $0.id == item }) else { return }
viewModel.select(account)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 92 }
}
private final class AccountSwitchCell: UITableViewCell {
static let reuseID = "AccountSwitchCell"
private let card = UIView()
private let avatarView = UIImageView()
private let titleStackView = UIStackView()
private let titleLabel = UILabel()
private let currentTagLabel = UILabel()
private let subtitleLabel = UILabel()
private let nameLabel = UILabel()
private let tagLabel = UILabel()
private let checkView = UIImageView()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
card.backgroundColor = .white
card.layer.cornerRadius = 12
contentView.addSubview(card)
avatarView.layer.cornerRadius = 25
avatarView.clipsToBounds = true
avatarView.contentMode = .scaleAspectFill
titleStackView.axis = .horizontal
titleStackView.alignment = .center
titleStackView.spacing = 6
titleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
titleLabel.numberOfLines = 1
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
currentTagLabel.font = .systemFont(ofSize: 11, weight: .regular)
currentTagLabel.text = "当前"
currentTagLabel.textColor = AppColor.primary
currentTagLabel.textAlignment = .center
currentTagLabel.backgroundColor = UIColor(hex: 0xEFF6FF)
currentTagLabel.layer.cornerRadius = 4
currentTagLabel.clipsToBounds = true
currentTagLabel.isHidden = true
currentTagLabel.setContentHuggingPriority(.required, for: .horizontal)
currentTagLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
subtitleLabel.font = .systemFont(ofSize: 13)
subtitleLabel.textColor = AppColor.text666
nameLabel.font = .systemFont(ofSize: 12)
nameLabel.textColor = UIColor(hex: 0x9AA1AA)
tagLabel.font = .systemFont(ofSize: 11, weight: .semibold)
tagLabel.textAlignment = .center
tagLabel.layer.cornerRadius = 4
tagLabel.clipsToBounds = true
card.addSubview(avatarView)
titleStackView.addArrangedSubview(titleLabel)
titleStackView.addArrangedSubview(currentTagLabel)
card.addSubview(titleStackView)
card.addSubview(subtitleLabel)
card.addSubview(nameLabel)
card.addSubview(tagLabel)
card.addSubview(checkView)
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.centerY.equalToSuperview().inset(14)
make.width.height.equalTo(50)
}
titleStackView.snp.makeConstraints { make in
make.leading.equalTo(avatarView.snp.trailing).offset(13)
make.top.equalTo(avatarView).offset(2)
make.trailing.lessThanOrEqualTo(tagLabel.snp.leading).offset(-8)
}
currentTagLabel.snp.makeConstraints { make in
make.height.equalTo(19)
make.width.greaterThanOrEqualTo(34)
}
subtitleLabel.snp.makeConstraints { make in
make.leading.equalTo(titleStackView)
make.top.equalTo(titleStackView.snp.bottom).offset(4)
}
nameLabel.snp.makeConstraints { make in
make.leading.equalTo(titleLabel)
make.top.equalTo(subtitleLabel.snp.bottom).offset(4)
}
tagLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().inset(14)
make.top.equalToSuperview().offset(14)
make.height.equalTo(22)
make.width.greaterThanOrEqualTo(40)
}
checkView.snp.makeConstraints { make in
make.trailing.bottom.equalToSuperview().inset(14)
make.width.height.equalTo(22)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
func configure(account: AccountSwitchAccount, selected: Bool, isCurrent: Bool) {
titleLabel.text = account.title
currentTagLabel.isHidden = !isCurrent
subtitleLabel.text = account.subtitle
subtitleLabel.isHidden = account.subtitle.isEmpty
nameLabel.text = account.realName
nameLabel.isHidden = account.realName.isEmpty
tagLabel.text = account.isStoreUser ? "门店" : "景区"
tagLabel.textColor = account.isStoreUser ? UIColor(hex: 0x0F9F6E) : UIColor(hex: 0x7C3AED)
tagLabel.backgroundColor = account.isStoreUser ? UIColor(hex: 0xE8F8F1) : UIColor(hex: 0xF3ECFF)
checkView.image = UIImage(systemName: selected ? "checkmark.circle.fill" : "circle")
checkView.tintColor = selected ? AppColor.primary : UIColor(hex: 0xB6BECA)
avatarView.loadRemoteImage(urlString: account.avatar)
}
}