// // TravelAlbumCodeDialogViewController.swift // suixinkan // import Kingfisher import SnapKit import UIKit /// 相册码弹窗,对齐 Android `TravelAlbumCodeDialog`。 final class TravelAlbumCodeDialogViewController: UIViewController { var onClose: (() -> Void)? var onDownload: (() -> Void)? private let state: TravelAlbumEntryViewModel.AlbumCodeState private let dimView = UIView() private let cardView = UIView() private let titleLabel = UILabel() private let closeButton = UIButton(type: .system) private let albumNameLabel = UILabel() private let qrImageView = UIImageView() private let loadingIndicator = UIActivityIndicatorView(style: .medium) private let hintLabel = TravelAlbumCodeHintLabel( contentInsets: UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) ) private let downloadButton = UIButton(type: .system) init(state: TravelAlbumEntryViewModel.AlbumCodeState) { self.state = state super.init(nibName: nil, bundle: nil) modalPresentationStyle = .overFullScreen modalTransitionStyle = .crossDissolve } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() setupUI() setupConstraints() applyState() } private func setupUI() { dimView.backgroundColor = UIColor.black.withAlphaComponent(0.35) cardView.backgroundColor = .white cardView.layer.cornerRadius = 16 cardView.clipsToBounds = true titleLabel.text = "相册码" titleLabel.font = .systemFont(ofSize: 18, weight: .bold) titleLabel.textColor = AppColor.textPrimary titleLabel.textAlignment = .center closeButton.setImage(UIImage(systemName: "xmark"), for: .normal) closeButton.tintColor = AppColor.textSecondary albumNameLabel.font = .systemFont(ofSize: 18, weight: .medium) albumNameLabel.textColor = AppColor.textPrimary albumNameLabel.textAlignment = .center qrImageView.contentMode = .scaleAspectFit qrImageView.layer.cornerRadius = 8 qrImageView.clipsToBounds = true hintLabel.textAlignment = .center hintLabel.font = .systemFont(ofSize: 13) hintLabel.textColor = AppColor.textSecondary hintLabel.backgroundColor = AppColor.primary.withAlphaComponent(0.06) hintLabel.layer.cornerRadius = 8 hintLabel.layer.borderWidth = 1 hintLabel.layer.borderColor = AppColor.primary.withAlphaComponent(0.35).cgColor hintLabel.clipsToBounds = true let hint = NSMutableAttributedString(string: "微信", attributes: [.foregroundColor: AppColor.textSecondary]) hint.append(NSAttributedString(string: "扫一扫", attributes: [.foregroundColor: AppColor.primary, .font: UIFont.systemFont(ofSize: 13, weight: .medium)])) hint.append(NSAttributedString(string: ",查看下载照片", attributes: [.foregroundColor: AppColor.textSecondary])) hintLabel.attributedText = hint downloadButton.setTitle("下载图片", for: .normal) downloadButton.setTitleColor(.white, for: .normal) downloadButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) downloadButton.backgroundColor = AppColor.primary downloadButton.layer.cornerRadius = 23 view.addSubview(dimView) view.addSubview(cardView) [titleLabel, closeButton, albumNameLabel, qrImageView, loadingIndicator, hintLabel, downloadButton].forEach(cardView.addSubview) closeButton.addTarget(self, action: #selector(closeTapped), for: .touchUpInside) downloadButton.addTarget(self, action: #selector(downloadTapped), for: .touchUpInside) } private func setupConstraints() { dimView.snp.makeConstraints { make in make.edges.equalToSuperview() } cardView.snp.makeConstraints { make in make.center.equalToSuperview() make.width.equalToSuperview().multipliedBy(0.78) } titleLabel.snp.makeConstraints { make in make.top.equalToSuperview().offset(18) make.leading.trailing.equalToSuperview().inset(48) } closeButton.snp.makeConstraints { make in make.centerY.equalTo(titleLabel) make.trailing.equalToSuperview().offset(-20) make.size.equalTo(28) } albumNameLabel.snp.makeConstraints { make in make.top.equalTo(titleLabel.snp.bottom).offset(18) make.leading.trailing.equalToSuperview().inset(20) } qrImageView.snp.makeConstraints { make in make.top.equalTo(albumNameLabel.snp.bottom).offset(18) make.centerX.equalToSuperview() make.size.equalTo(190) } loadingIndicator.snp.makeConstraints { make in make.center.equalTo(qrImageView) } hintLabel.snp.makeConstraints { make in make.top.equalTo(qrImageView.snp.bottom).offset(18) make.centerX.equalToSuperview() make.leading.greaterThanOrEqualToSuperview().offset(20) make.trailing.lessThanOrEqualToSuperview().offset(-20) } downloadButton.snp.makeConstraints { make in make.top.equalTo(hintLabel.snp.bottom).offset(20) make.leading.trailing.equalToSuperview().inset(20) make.height.equalTo(46) make.bottom.equalToSuperview().offset(-18) } } private func applyState() { albumNameLabel.text = state.albumName.isEmpty ? "未命名相册" : state.albumName if state.isLoading { loadingIndicator.startAnimating() qrImageView.image = nil } else { loadingIndicator.stopAnimating() if let url = URL(string: state.qrImageUrl), !state.qrImageUrl.isEmpty { qrImageView.kf.setImage(with: url) } else { qrImageView.image = UIImage(systemName: "qrcode") qrImageView.tintColor = AppColor.textSecondary } } downloadButton.isEnabled = !state.isLoading && !state.qrImageUrl.isEmpty downloadButton.alpha = downloadButton.isEnabled ? 1 : 0.5 } func updateDownloading(_ isDownloading: Bool) { downloadButton.setTitle(isDownloading ? "保存中..." : "下载图片", for: .normal) downloadButton.isEnabled = !isDownloading } @objc private func closeTapped() { onClose?() dismiss(animated: true) } @objc private func downloadTapped() { onDownload?() } } /// 为相册码提示文案提供与 Android 弹窗一致的内容留白。 private final class TravelAlbumCodeHintLabel: UILabel { private let contentInsets: UIEdgeInsets init(contentInsets: UIEdgeInsets) { self.contentInsets = contentInsets super.init(frame: .zero) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: contentInsets)) } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize( width: size.width + contentInsets.left + contentInsets.right, height: size.height + contentInsets.top + contentInsets.bottom ) } }