// // SettingViewController.swift // suixinkan // import SnapKit import UIKit /// 设置中心页面,对齐 Android `SettingScreen` 的当前可见设置项。 final class SettingViewController: BaseViewController { private let viewModel: SettingViewModel private let settingAPI: SettingAPI private let contentView = UIView() private let cardView = UIView() private let rowsStack = UIStackView() private let versionRow = SettingMenuRow(title: "系统版本", showsChevron: false) private let copyrightLabel = UILabel() init( viewModel: SettingViewModel = SettingViewModel(), settingAPI: SettingAPI = NetworkServices.shared.settingAPI ) { self.viewModel = viewModel self.settingAPI = settingAPI super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func setupNavigationBar() { title = "设置" } override func setupUI() { view.backgroundColor = AppColor.pageBackground view.addSubview(contentView) contentView.addSubview(cardView) cardView.addSubview(rowsStack) contentView.addSubview(copyrightLabel) cardView.backgroundColor = .white cardView.layer.cornerRadius = 12 cardView.clipsToBounds = true rowsStack.axis = .vertical copyrightLabel.text = "Copyright © 2025 All Rights Reserved\n苏ICP备2025157647号" copyrightLabel.font = .systemFont(ofSize: 14) copyrightLabel.textColor = UIColor(hex: 0xB6BECA) copyrightLabel.textAlignment = .center copyrightLabel.numberOfLines = 0 let rows: [SettingMenuRow] = [ SettingMenuRow(title: "关于我们"), versionRow, SettingMenuRow(title: "App下载", value: "复制链接", valueColor: AppColor.primary, showsChevron: false), SettingMenuRow(title: "用户协议"), SettingMenuRow(title: "隐私政策", showsDivider: false), ] rows.forEach(rowsStack.addArrangedSubview) rows[0].addTarget(self, action: #selector(aboutTapped), for: .touchUpInside) rows[2].addTarget(self, action: #selector(copyDownloadTapped), for: .touchUpInside) rows[3].addTarget(self, action: #selector(userAgreementTapped), for: .touchUpInside) rows[4].addTarget(self, action: #selector(privacyTapped), for: .touchUpInside) } override func setupConstraints() { contentView.snp.makeConstraints { make in make.edges.equalTo(view.safeAreaLayoutGuide).inset(16) } cardView.snp.makeConstraints { make in make.top.leading.trailing.equalToSuperview() } rowsStack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)) } copyrightLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.bottom.equalToSuperview().offset(-16) } } override func bindActions() { viewModel.onStateChange = { [weak self] in Task { @MainActor in self?.applyViewModel() } } } override func viewDidLoad() { super.viewDidLoad() applyViewModel() Task { await viewModel.checkLatestVersion(api: settingAPI) } } private func applyViewModel() { versionRow.configure(value: viewModel.appVersion, showsUpgrade: viewModel.hasNewVersion) } @objc private func aboutTapped() { openAgreement(.aboutUs) } @objc private func copyDownloadTapped() { UIPasteboard.general.string = viewModel.appDownloadURL.absoluteString showToast("已复制到剪贴板!") } @objc private func userAgreementTapped() { openAgreement(.userAgreement) } @objc private func privacyTapped() { openAgreement(.privacyPolicy) } private func openAgreement(_ kind: SettingAgreementKind) { let destination = viewModel.agreementDestination(for: kind) navigationController?.pushViewController( AgreementWebViewController(title: destination.title, url: destination.url), animated: true ) } } /// 设置中心菜单行,展示标题、右侧文本、更新标记和箭头。 final class SettingMenuRow: UIControl { private let titleLabel = UILabel() private let rightStack = UIStackView() private let valueLabel = UILabel() private let upgradeImageView = UIImageView() private let chevronImageView = UIImageView() private let divider = UIView() private let showsChevron: Bool init( title: String, value: String? = nil, valueColor: UIColor = AppColor.textPrimary, showsChevron: Bool = true, showsDivider: Bool = true ) { self.showsChevron = showsChevron super.init(frame: .zero) setupUI() setupConstraints() titleLabel.text = title valueLabel.text = value valueLabel.textColor = valueColor chevronImageView.isHidden = !showsChevron divider.isHidden = !showsDivider accessibilityTraits.insert(.button) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var isHighlighted: Bool { didSet { backgroundColor = isHighlighted ? UIColor.black.withAlphaComponent(0.04) : .clear } } func configure(value: String?, showsUpgrade: Bool) { valueLabel.text = value upgradeImageView.isHidden = !showsUpgrade } private func setupUI() { titleLabel.font = .systemFont(ofSize: 14) titleLabel.textColor = UIColor(hex: 0x4B5563) valueLabel.font = .systemFont(ofSize: 14) valueLabel.textColor = AppColor.textPrimary upgradeImageView.image = UIImage(systemName: "arrow.up.circle")?.withRenderingMode(.alwaysTemplate) upgradeImageView.tintColor = UIColor(hex: 0x1F1F1F) upgradeImageView.contentMode = .scaleAspectFit upgradeImageView.isHidden = true chevronImageView.image = UIImage(systemName: "chevron.right")?.withRenderingMode(.alwaysTemplate) chevronImageView.tintColor = AppColor.textTertiary chevronImageView.contentMode = .scaleAspectFit divider.backgroundColor = AppColor.border rightStack.axis = .horizontal rightStack.alignment = .center rightStack.spacing = 4 [valueLabel, upgradeImageView, chevronImageView].forEach(rightStack.addArrangedSubview) [titleLabel, rightStack, divider].forEach(addSubview) } private func setupConstraints() { snp.makeConstraints { make in make.height.equalTo(52) } titleLabel.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() } upgradeImageView.snp.makeConstraints { make in make.width.height.equalTo(24) } chevronImageView.snp.makeConstraints { make in make.width.height.equalTo(24) } rightStack.snp.makeConstraints { make in make.centerY.equalToSuperview() make.trailing.equalToSuperview() make.leading.greaterThanOrEqualTo(titleLabel.snp.trailing).offset(12) } divider.snp.makeConstraints { make in make.leading.trailing.bottom.equalToSuperview() make.height.equalTo(0.5) } } }