feat: 更新素材管理和举报风险地图

This commit is contained in:
2026-07-09 12:20:02 +08:00
parent 9b92d81902
commit 42aca73588
42 changed files with 6164 additions and 1369 deletions

View File

@ -0,0 +1,67 @@
//
// AgreementWebViewController.swift
// suixinkan
//
import SnapKit
import UIKit
import WebKit
/// H5 使 WebView
final class AgreementWebViewController: BaseViewController {
private let pageTitle: String
private let url: URL
private let webView = WKWebView(frame: .zero)
private let loadingIndicator = UIActivityIndicatorView(style: .medium)
init(title: String, url: URL) {
pageTitle = title
self.url = url
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = pageTitle
}
override func setupUI() {
view.backgroundColor = AppColor.cardBackground
webView.navigationDelegate = self
view.addSubview(webView)
view.addSubview(loadingIndicator)
loadingIndicator.hidesWhenStopped = true
loadingIndicator.startAnimating()
webView.load(URLRequest(url: url))
}
override func setupConstraints() {
webView.snp.makeConstraints { make in
make.edges.equalTo(view.safeAreaLayoutGuide)
}
loadingIndicator.snp.makeConstraints { make in
make.center.equalToSuperview()
}
}
}
extension AgreementWebViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
loadingIndicator.stopAnimating()
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
loadingIndicator.stopAnimating()
showToast(error.localizedDescription)
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
loadingIndicator.stopAnimating()
showToast(error.localizedDescription)
}
}

View File

@ -0,0 +1,230 @@
//
// 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)
}
}
}