Advance UIKit rewrite with AMap integration and core UI modules.

Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 15:16:12 +08:00
parent 9edf993432
commit d99a5b1bf8
124 changed files with 5195 additions and 1536 deletions

View File

@ -6,8 +6,18 @@
import SnapKit
import UIKit
// MARK: - Diffable
private typealias AccountSwitchSection = Int
private typealias AccountSwitchItem = String
///
private enum AccountSwitchItemID {
static let empty = "accountSwitch:empty"
}
///
final class AccountSwitchViewController: UIViewController {
final class AccountSwitchViewController: UIViewController, UITableViewDelegate {
private let viewModel = AccountSwitchViewModel()
@ -15,7 +25,6 @@ final class AccountSwitchViewController: UIViewController {
let table = UITableView(frame: .zero, style: .plain)
table.backgroundColor = UIColor(hex: 0xF5F7FB)
table.separatorStyle = .none
table.dataSource = self
table.delegate = self
table.register(AccountSwitchCell.self, forCellReuseIdentifier: AccountSwitchCell.reuseID)
return table
@ -27,11 +36,16 @@ final class AccountSwitchViewController: UIViewController {
return button
}()
/// Diffable
private var tableDataSource: UITableViewDiffableDataSource<AccountSwitchSection, AccountSwitchItem>!
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "账号切换"
view.backgroundColor = UIColor(hex: 0xF5F7FB)
configureTableDataSource()
view.addSubview(tableView)
view.addSubview(confirmButton)
tableView.snp.makeConstraints { make in
@ -45,18 +59,75 @@ final class AccountSwitchViewController: UIViewController {
}
viewModel.onChange = { [weak self] in
self?.tableView.reloadData()
self?.applyTableSnapshot(reconfigure: true)
self?.updateConfirmButton()
}
Task { await loadAccounts() }
}
/// Diffable
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<AccountSwitchSection, AccountSwitchItem>(
tableView: tableView
) { [weak self] (tableView: UITableView, indexPath: IndexPath, item: AccountSwitchItem) -> UITableViewCell? in
guard let self else { return UITableViewCell() }
if item == AccountSwitchItemID.empty {
let cell = UITableViewCell()
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let empty = makeEmptyStateView(
title: "暂无可切换账号",
message: "当前登录账号下没有其他可切换账号。",
systemImage: "person.crop.circle.badge.exclamationmark"
)
cell.contentView.addSubview(empty)
empty.snp.makeConstraints { make in make.edges.equalToSuperview(); make.height.equalTo(280) }
return cell
}
guard let account = self.viewModel.accounts.first(where: { $0.id == item }) else {
return UITableViewCell()
}
let cell = tableView.dequeueReusableCell(withIdentifier: AccountSwitchCell.reuseID, for: indexPath) as! AccountSwitchCell
cell.configure(
account: account,
selected: self.viewModel.selectedAccountId == account.id,
isCurrent: account.isCurrent || self.isCurrentAccount(account)
)
return cell
}
applyTableSnapshot(animated: false)
}
/// Diffable snapshot
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<AccountSwitchSection, AccountSwitchItem> {
var snapshot = NSDiffableDataSourceSnapshot<AccountSwitchSection, AccountSwitchItem>()
snapshot.appendSections([0])
if viewModel.accounts.isEmpty, !viewModel.loading {
snapshot.appendItems([AccountSwitchItemID.empty], toSection: 0)
} else {
snapshot.appendItems(viewModel.accounts.map(\.id), toSection: 0)
}
return snapshot
}
/// snapshot
private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) {
var snapshot = buildTableSnapshot()
if reconfigure, !snapshot.itemIdentifiers.isEmpty {
snapshot.reconfigureItems(snapshot.itemIdentifiers)
}
tableDataSource.apply(snapshot, animatingDifferences: animated)
}
/// ConfirmButton
private func updateConfirmButton() {
let enabled = viewModel.selectedAccount != nil && !viewModel.loading && !viewModel.switching
confirmButton.isEnabled = enabled
confirmButton.alpha = enabled ? 1 : 0.5
}
/// Accounts
private func loadAccounts(force: Bool = false) async {
do {
try await appServices.globalLoading.withOptionalLoading(!force && viewModel.accounts.isEmpty, message: "加载中...") {
@ -82,10 +153,12 @@ final class AccountSwitchViewController: UIViewController {
return current.userId.isEmpty ? nil : current.userId
}
/// isCurrentAccount
private func isCurrentAccount(_ account: AccountSwitchAccount) -> Bool {
viewModel.isCurrent(account, currentAccountId: currentAccountId)
}
/// confirm
@objc private func confirmTapped() {
guard let account = viewModel.selectedAccount else { return }
if account.isCurrent || isCurrentAccount(account) {
@ -95,6 +168,7 @@ final class AccountSwitchViewController: UIViewController {
Task { await switchAccount(account) }
}
/// switch
private func switchAccount(_ account: AccountSwitchAccount) async {
do {
let response = try await appServices.globalLoading.withLoading(message: "切换中...") {
@ -121,53 +195,29 @@ final class AccountSwitchViewController: UIViewController {
}
}
/// non
private func nonEmpty(_ value: String?) -> String? {
let text = value?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
return text.isEmpty ? nil : text
}
}
extension AccountSwitchViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.accounts.isEmpty && !viewModel.loading ? 1 : viewModel.accounts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard !viewModel.accounts.isEmpty else {
let cell = UITableViewCell()
cell.selectionStyle = .none
cell.backgroundColor = .clear
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
let empty = makeEmptyStateView(
title: "暂无可切换账号",
message: "当前登录账号下没有其他可切换账号。",
systemImage: "person.crop.circle.badge.exclamationmark"
)
cell.contentView.addSubview(empty)
empty.snp.makeConstraints { make in make.edges.equalToSuperview(); make.height.equalTo(280) }
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: AccountSwitchCell.reuseID, for: indexPath) as! AccountSwitchCell
let account = viewModel.accounts[indexPath.row]
cell.configure(
account: account,
selected: viewModel.selectedAccountId == account.id,
isCurrent: account.isCurrent || isCurrentAccount(account)
)
return cell
}
/// UITableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
guard indexPath.row < viewModel.accounts.count else { return }
viewModel.select(viewModel.accounts[indexPath.row])
guard let item = tableDataSource.itemIdentifier(for: indexPath),
item != AccountSwitchItemID.empty,
let account = viewModel.accounts.first(where: { $0.id == item }) else { return }
viewModel.select(account)
}
/// UITableView
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
viewModel.accounts.isEmpty ? 280 : 92
guard let item = tableDataSource.itemIdentifier(for: indexPath) else { return 92 }
return item == AccountSwitchItemID.empty ? 280 : 92
}
}
/// AccountSwitch Cell
private final class AccountSwitchCell: UITableViewCell {
static let reuseID = "AccountSwitchCell"
@ -178,6 +228,7 @@ private final class AccountSwitchCell: UITableViewCell {
private let tagLabel = UILabel()
private let checkView = UIImageView()
///
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
@ -245,6 +296,7 @@ private final class AccountSwitchCell: UITableViewCell {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
///
func configure(account: AccountSwitchAccount, selected: Bool, isCurrent: Bool) {
let title = account.title.trimmingCharacters(in: .whitespacesAndNewlines)
titleLabel.text = title.isEmpty ? account.accountTypeLabel : title

View File

@ -7,8 +7,29 @@ import PhotosUI
import SnapKit
import UIKit
// MARK: - Diffable
private typealias ProfileTableSection = Int
private typealias ProfileTableRow = String
/// section
private enum ProfileSectionID {
static let info = 0
static let logout = 1
}
///
private enum ProfileRowID {
static let name = "profile:name"
static let account = "profile:account"
static let phone = "profile:phone"
static let realName = "profile:realName"
static let settings = "profile:settings"
static let logout = "profile:logout"
}
///
final class ProfileViewController: UIViewController {
final class ProfileViewController: UIViewController, UITableViewDelegate {
private let viewModel = ProfileViewModel()
@ -19,18 +40,22 @@ final class ProfileViewController: UIViewController {
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .insetGrouped)
table.dataSource = self
table.delegate = self
return table
}()
private lazy var refreshControl = UIRefreshControl()
/// Diffable
private var tableDataSource: UITableViewDiffableDataSource<ProfileTableSection, ProfileTableRow>!
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "个人信息"
view.backgroundColor = UIColor(hex: 0xF7FAFF)
setupHeader()
configureTableDataSource()
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(150)
@ -49,6 +74,74 @@ final class ProfileViewController: UIViewController {
Task { await reloadProfile(showToast: false) }
}
/// Diffable
private func configureTableDataSource() {
tableDataSource = UITableViewDiffableDataSource<ProfileTableSection, ProfileTableRow>(
tableView: tableView
) { [weak self] (_: UITableView, indexPath: IndexPath, row: ProfileTableRow) -> UITableViewCell? in
guard let self else { return UITableViewCell() }
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
switch row {
case ProfileRowID.logout:
cell.textLabel?.text = "退出登录"
cell.textLabel?.textColor = UIColor(hex: 0xEF4444)
cell.textLabel?.textAlignment = .center
cell.selectionStyle = .default
case ProfileRowID.name:
cell.textLabel?.text = "姓名"
cell.detailTextLabel?.text = self.viewModel.displayRealName
cell.selectionStyle = .none
case ProfileRowID.account:
cell.textLabel?.text = "当前账号"
cell.detailTextLabel?.text = self.appServices.accountContext.profile?.displayName ?? "--"
cell.accessoryType = .disclosureIndicator
cell.selectionStyle = .default
case ProfileRowID.phone:
cell.textLabel?.text = "手机号"
cell.detailTextLabel?.text = self.viewModel.displayPhone
cell.selectionStyle = .none
case ProfileRowID.realName:
cell.textLabel?.text = "实名认证"
cell.detailTextLabel?.text = self.viewModel.realNameStatusText
cell.accessoryType = .disclosureIndicator
cell.selectionStyle = .default
case ProfileRowID.settings:
cell.textLabel?.text = "设置"
cell.accessoryType = .disclosureIndicator
cell.selectionStyle = .default
default:
break
}
return cell
}
applyTableSnapshot(animated: false)
}
/// Diffable snapshot
private func buildTableSnapshot() -> NSDiffableDataSourceSnapshot<ProfileTableSection, ProfileTableRow> {
var snapshot = NSDiffableDataSourceSnapshot<ProfileTableSection, ProfileTableRow>()
snapshot.appendSections([ProfileSectionID.info, ProfileSectionID.logout])
snapshot.appendItems([
ProfileRowID.name,
ProfileRowID.account,
ProfileRowID.phone,
ProfileRowID.realName,
ProfileRowID.settings
], toSection: ProfileSectionID.info)
snapshot.appendItems([ProfileRowID.logout], toSection: ProfileSectionID.logout)
return snapshot
}
/// snapshot Cell
private func applyTableSnapshot(animated: Bool = true, reconfigure: Bool = false) {
var snapshot = buildTableSnapshot()
if reconfigure {
snapshot.reconfigureItems(snapshot.itemIdentifiers)
}
tableDataSource.apply(snapshot, animatingDifferences: animated)
}
/// Header UI
private func setupHeader() {
let header = UIView()
header.backgroundColor = .white
@ -101,6 +194,7 @@ final class ProfileViewController: UIViewController {
}
}
/// apply
private func applyViewModel() {
nicknameLabel.text = viewModel.displayNickname
uidLabel.text = "UID: \(appServices.accountContext.profile?.userId ?? "--")"
@ -111,9 +205,10 @@ final class ProfileViewController: UIViewController {
}
editButton.setImage(UIImage(systemName: viewModel.isEditingProfile ? "checkmark" : "pencil"), for: .normal)
editButton.isEnabled = !viewModel.isSaving
tableView.reloadData()
applyTableSnapshot(animated: false, reconfigure: true)
}
/// Pulled
@objc private func refreshPulled() {
Task {
await reloadProfile(showToast: true)
@ -121,6 +216,7 @@ final class ProfileViewController: UIViewController {
}
}
/// Profile
private func reloadProfile(showToast: Bool) async {
do {
try await appServices.globalLoading.withOptionalLoading(!showToast && viewModel.userInfo == nil, message: "加载资料...") {
@ -131,6 +227,7 @@ final class ProfileViewController: UIViewController {
}
}
/// edit
@objc private func editTapped() {
if viewModel.isEditingProfile {
Task { await saveProfileEdits() }
@ -140,6 +237,7 @@ final class ProfileViewController: UIViewController {
}
}
/// NicknameEditor
private func presentNicknameEditor() {
let alert = UIAlertController(title: "编辑昵称", message: nil, preferredStyle: .alert)
alert.addTextField { [weak self] field in
@ -157,6 +255,7 @@ final class ProfileViewController: UIViewController {
present(alert, animated: true)
}
/// saveEdits
private func saveProfileEdits() async {
guard let scenicId = appServices.accountContext.currentScenic?.id else {
showToast("请先选择景区")
@ -176,6 +275,7 @@ final class ProfileViewController: UIViewController {
}
}
/// pick
@objc private func pickAvatar() {
var config = PHPickerConfiguration()
config.filter = .images
@ -185,6 +285,7 @@ final class ProfileViewController: UIViewController {
present(picker, animated: true)
}
/// logout
@objc private func logoutTapped() {
let alert = UIAlertController(title: "确认退出当前账号?", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
@ -201,58 +302,19 @@ final class ProfileViewController: UIViewController {
})
present(alert, animated: true)
}
}
extension ProfileViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int { 2 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
section == 0 ? 5 : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
if indexPath.section == 1 {
cell.textLabel?.text = "退出登录"
cell.textLabel?.textColor = UIColor(hex: 0xEF4444)
cell.textLabel?.textAlignment = .center
return cell
}
switch indexPath.row {
case 0:
cell.textLabel?.text = "姓名"
cell.detailTextLabel?.text = viewModel.displayRealName
case 1:
cell.textLabel?.text = "当前账号"
cell.detailTextLabel?.text = appServices.accountContext.profile?.displayName ?? "--"
cell.accessoryType = .disclosureIndicator
case 2:
cell.textLabel?.text = "手机号"
cell.detailTextLabel?.text = viewModel.displayPhone
case 3:
cell.textLabel?.text = "实名认证"
cell.detailTextLabel?.text = viewModel.realNameStatusText
cell.accessoryType = .disclosureIndicator
default:
cell.textLabel?.text = "设置"
cell.accessoryType = .disclosureIndicator
}
cell.selectionStyle = indexPath.row == 0 || indexPath.row == 2 ? .none : .default
return cell
}
/// UITableView
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.section == 1 {
guard let row = tableDataSource.itemIdentifier(for: indexPath) else { return }
switch row {
case ProfileRowID.logout:
logoutTapped()
return
}
switch indexPath.row {
case 1:
case ProfileRowID.account:
HomeMenuRouting.pushProfile(.accountSwitch, from: self)
case 3:
case ProfileRowID.realName:
HomeMenuRouting.pushProfile(.realNameAuth, from: self)
case 4:
case ProfileRowID.settings:
HomeMenuRouting.pushProfile(.settings, from: self)
default:
break
@ -261,6 +323,7 @@ extension ProfileViewController: UITableViewDataSource, UITableViewDelegate {
}
extension ProfileViewController: PHPickerViewControllerDelegate {
/// picker
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else { return }

View File

@ -25,6 +25,7 @@ final class RealNameAuthViewController: UIViewController {
private let startDatePicker = UIDatePicker()
private let endDatePicker = UIDatePicker()
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "实名认证"
@ -34,6 +35,7 @@ final class RealNameAuthViewController: UIViewController {
Task { await loadInfo() }
}
/// Form UI
private func setupForm() {
contentStack.axis = .vertical
contentStack.spacing = 16
@ -107,6 +109,7 @@ final class RealNameAuthViewController: UIViewController {
contentStack.addArrangedSubview(submitButton)
}
/// SectionTitle
private func makeSectionTitle(_ text: String) -> UILabel {
let label = UILabel()
label.text = text
@ -114,6 +117,7 @@ final class RealNameAuthViewController: UIViewController {
return label
}
/// labeled
private func labeledField(_ title: String, _ field: UITextField) -> UIStackView {
let label = UILabel()
label.text = title
@ -125,6 +129,7 @@ final class RealNameAuthViewController: UIViewController {
return stack
}
/// apply
private func applyViewModel() {
realNameField.text = viewModel.realName
idCardField.text = viewModel.idCardNo
@ -163,6 +168,7 @@ final class RealNameAuthViewController: UIViewController {
}
}
/// Info
private func loadInfo() async {
do {
try await appServices.globalLoading.withLoading(message: "加载中...") {
@ -173,10 +179,12 @@ final class RealNameAuthViewController: UIViewController {
}
}
/// longValid
@objc private func longValidChanged() {
viewModel.isLongValid = longValidSwitch.isOn
}
/// send
@objc private func sendCodeTapped() {
Task {
do {
@ -187,6 +195,7 @@ final class RealNameAuthViewController: UIViewController {
}
}
/// Tapped
@objc private func submitTapped() {
viewModel.realName = realNameField.text ?? ""
viewModel.idCardNo = idCardField.text ?? ""
@ -215,16 +224,19 @@ final class RealNameAuthViewController: UIViewController {
private var pendingSide: RealNameImageSide = .front
/// pickFront
@objc private func pickFront() {
pendingSide = .front
presentImagePicker()
}
/// pickBack
@objc private func pickBack() {
pendingSide = .back
presentImagePicker()
}
/// ImagePicker
private func presentImagePicker() {
var config = PHPickerConfiguration()
config.filter = .images
@ -236,6 +248,7 @@ final class RealNameAuthViewController: UIViewController {
}
extension RealNameAuthViewController: PHPickerViewControllerDelegate {
/// picker
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
guard let provider = results.first?.itemProvider, provider.canLoadObject(ofClass: UIImage.self) else { return }
@ -253,6 +266,7 @@ extension RealNameAuthViewController: PHPickerViewControllerDelegate {
}
private extension UILabel {
///
convenience init(text: String) {
self.init()
self.text = text

View File

@ -7,64 +7,81 @@ import SnapKit
import UIKit
import WebKit
// MARK: - Diffable
///
private enum SettingsRowID {
static let about = "settings:about"
static let version = "settings:version"
static let download = "settings:download"
static let userAgreement = "settings:userAgreement"
static let privacyPolicy = "settings:privacyPolicy"
}
///
private enum SettingsRowAction {
case agreement(AgreementPage)
case version
case download
}
///
final class SettingsViewController: UIViewController {
final class SettingsViewController: SimpleTableDiffableViewController {
private var copiedDownloadLink = false
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .insetGrouped)
table.dataSource = self
table.delegate = self
return table
}()
private let rows: [(title: String, action: SettingsRowAction)] = [
("关于我们", .agreement(.about)),
("系统版本", .version),
("App下载", .download),
("用户协议", .agreement(.userAgreement)),
("隐私政策", .agreement(.privacyPolicy))
private let rowActions: [String: SettingsRowAction] = [
SettingsRowID.about: .agreement(.about),
SettingsRowID.version: .version,
SettingsRowID.download: .download,
SettingsRowID.userAgreement: .agreement(.userAgreement),
SettingsRowID.privacyPolicy: .agreement(.privacyPolicy)
]
private enum SettingsRowAction {
case agreement(AgreementPage)
case version
case download
}
private let rowTitles: [String: String] = [
SettingsRowID.about: "关于我们",
SettingsRowID.version: "系统版本",
SettingsRowID.download: "App下载",
SettingsRowID.userAgreement: "用户协议",
SettingsRowID.privacyPolicy: "隐私政策"
]
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "设置"
view.addSubview(tableView)
tableView.snp.makeConstraints { make in make.edges.equalToSuperview() }
}
private var downloadLink: String {
APIEnvironment.current.baseURL.appending(path: "/h5/app/download").absoluteString
/// Diffable snapshot
override func buildSnapshot() -> NSDiffableDataSourceSnapshot<SimpleTableSection, SimpleTableRow> {
var snapshot = NSDiffableDataSourceSnapshot<SimpleTableSection, SimpleTableRow>()
snapshot.appendSections([0])
snapshot.appendItems([
SettingsRowID.about,
SettingsRowID.version,
SettingsRowID.download,
SettingsRowID.userAgreement,
SettingsRowID.privacyPolicy
], toSection: 0)
return snapshot
}
private func copyDownloadLink() {
UIPasteboard.general.string = downloadLink
copiedDownloadLink = true
showToast("下载链接已复制")
tableView.reloadRows(at: [IndexPath(row: 2, section: 0)], with: .none)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
self?.copiedDownloadLink = false
self?.tableView.reloadRows(at: [IndexPath(row: 2, section: 0)], with: .none)
}
/// section
override func sectionFooter(for section: SimpleTableSection) -> String? {
"Copyright © 2025 All Rights Reserved\n苏ICP备2025157647号"
}
}
extension SettingsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { rows.count }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
let row = rows[indexPath.row]
cell.textLabel?.text = row.title
/// Cell
override func configureCell(_ cell: UITableViewCell, row: SimpleTableRow, at indexPath: IndexPath) {
cell.textLabel?.text = rowTitles[row]
cell.textLabel?.textColor = UIColor(hex: 0x4B5563)
switch row.action {
cell.detailTextLabel?.text = nil
cell.detailTextLabel?.textColor = nil
cell.accessoryType = .none
cell.selectionStyle = .default
guard let action = rowActions[row] else { return }
switch action {
case .version:
cell.detailTextLabel?.text = SettingsDisplayPolicy.versionText()
cell.selectionStyle = .none
@ -74,12 +91,12 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate {
case .agreement:
cell.accessoryType = .disclosureIndicator
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
switch rows[indexPath.row].action {
///
override func didSelectRow(_ row: SimpleTableRow, at indexPath: IndexPath) {
guard let action = rowActions[row] else { return }
switch action {
case .agreement(let page):
HomeMenuRouting.pushProfile(.agreement(page), from: self)
case .download:
@ -89,8 +106,25 @@ extension SettingsViewController: UITableViewDataSource, UITableViewDelegate {
}
}
func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
"Copyright © 2025 All Rights Reserved\n苏ICP备2025157647号"
private var downloadLink: String {
APIEnvironment.current.baseURL.appending(path: "/h5/app/download").absoluteString
}
///
private func copyDownloadLink() {
UIPasteboard.general.string = downloadLink
copiedDownloadLink = true
showToast("下载链接已复制")
var snapshot = tableDataSource.snapshot()
snapshot.reconfigureItems([SettingsRowID.download])
tableDataSource.apply(snapshot, animatingDifferences: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
guard let self else { return }
self.copiedDownloadLink = false
var snapshot = self.tableDataSource.snapshot()
snapshot.reconfigureItems([SettingsRowID.download])
self.tableDataSource.apply(snapshot, animatingDifferences: false)
}
}
}
@ -101,6 +135,7 @@ final class AgreementViewController: UIViewController {
private let webView = WKWebView(frame: .zero)
private let loadingIndicator = UIActivityIndicatorView(style: .large)
///
init(page: AgreementPage) {
self.page = page
super.init(nibName: nil, bundle: nil)
@ -109,6 +144,7 @@ final class AgreementViewController: UIViewController {
@available(*, unavailable)
required init?(coder: NSCoder) { fatalError() }
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = page.title
@ -124,10 +160,12 @@ final class AgreementViewController: UIViewController {
}
extension AgreementViewController: WKNavigationDelegate {
/// web
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
loadingIndicator.stopAnimating()
}
/// web
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
loadingIndicator.stopAnimating()
showToast(error.localizedDescription)