231 lines
7.5 KiB
Swift
231 lines
7.5 KiB
Swift
//
|
||
// 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)
|
||
}
|
||
}
|
||
}
|