feat: 更新素材管理和举报风险地图
This commit is contained in:
67
suixinkan/UI/Setting/AgreementWebViewController.swift
Normal file
67
suixinkan/UI/Setting/AgreementWebViewController.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
230
suixinkan/UI/Setting/SettingViewController.swift
Normal file
230
suixinkan/UI/Setting/SettingViewController.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user