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,6 +6,19 @@
import SnapKit
import UIKit
// MARK: - Diffable
/// section
private enum HomeMoreSection: Hashable {
case commonApps
case moreFunctions
}
/// item URI
private enum HomeMoreItem: Hashable {
case menu(uri: String)
}
///
final class HomeMoreFunctionsViewController: UIViewController {
@ -13,29 +26,38 @@ final class HomeMoreFunctionsViewController: UIViewController {
private let commonMenuStore = HomeCommonMenuStore()
private var commonURIs: [String] = []
private lazy var tableView: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.backgroundColor = AppDesignUIKit.pageBackground
table.separatorStyle = .none
table.dataSource = self
table.delegate = self
table.register(HomeMoreMenuGridCell.self, forCellReuseIdentifier: HomeMoreMenuGridCell.reuseID)
return table
private lazy var collectionView: UICollectionView = {
let layout = makeCollectionLayout()
let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
collection.backgroundColor = AppDesignUIKit.pageBackground
collection.delegate = self
collection.register(HomeMoreMenuItemCell.self, forCellWithReuseIdentifier: HomeMoreMenuItemCell.reuseID)
collection.register(
CollectionSectionHeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: CollectionSectionHeaderView.reuseID
)
return collection
}()
/// Diffable section
private var dataSource: UICollectionViewDiffableDataSource<HomeMoreSection, HomeMoreItem>!
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "全部功能"
view.backgroundColor = AppDesignUIKit.pageBackground
navigationItem.largeTitleDisplayMode = .never
view.addSubview(tableView)
tableView.snp.makeConstraints { make in
configureDataSource()
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
viewModel.onChange = { [weak self] in
self?.tableView.reloadData()
self?.applySnapshot()
}
rebuildMenus()
@ -44,6 +66,95 @@ final class HomeMoreFunctionsViewController: UIViewController {
}
}
/// Compositional Layout section
private func makeCollectionLayout() -> UICollectionViewCompositionalLayout {
UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
guard let self,
let section = self.dataSource?.snapshot().sectionIdentifiers[safe: sectionIndex]
else {
return CollectionDiffableLayout.gridSection(itemHeight: 112)
}
let gridInsets = NSDirectionalEdgeInsets(
top: AppMetrics.Spacing.xxSmall,
leading: AppMetrics.Spacing.mediumLarge,
bottom: AppMetrics.Spacing.xxSmall,
trailing: AppMetrics.Spacing.mediumLarge
)
let grid = CollectionDiffableLayout.gridSection(
columns: 3,
itemHeight: 112,
interItemSpacing: 14,
lineSpacing: AppMetrics.Spacing.mediumLarge,
contentInsets: gridInsets
)
switch section {
case .commonApps:
return CollectionDiffableLayout.addHeader(to: grid, height: 36)
case .moreFunctions:
return CollectionDiffableLayout.addHeader(to: grid, height: 36)
}
}
}
/// Diffable Cell
private func configureDataSource() {
dataSource = UICollectionViewDiffableDataSource<HomeMoreSection, HomeMoreItem>(
collectionView: collectionView
) { [weak self] collectionView, indexPath, item in
guard let self,
case .menu(let uri) = item,
let section = self.dataSource.snapshot().sectionIdentifiers[safe: indexPath.section]
else { return UICollectionViewCell() }
let cell = collectionView.dequeueReusableCell(
withReuseIdentifier: HomeMoreMenuItemCell.reuseID,
for: indexPath
) as! HomeMoreMenuItemCell
let isCommon = section == .commonApps
let items = isCommon ? self.commonItems : self.moreItems
guard let menuItem = items.first(where: { $0.uri == uri }) else { return cell }
cell.configure(item: menuItem, isCommon: isCommon) { [weak self] in
self?.toggleCommon(menuItem, isCommon: isCommon)
}
return cell
}
dataSource.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
guard let self,
kind == UICollectionView.elementKindSectionHeader,
let section = self.dataSource.snapshot().sectionIdentifiers[safe: indexPath.section],
let header = collectionView.dequeueReusableSupplementaryView(
ofKind: kind,
withReuseIdentifier: CollectionSectionHeaderView.reuseID,
for: indexPath
) as? CollectionSectionHeaderView
else { return nil }
let title = section == .commonApps ? "常用应用" : "更多功能"
header.configure(title: title)
return header
}
}
/// snapshot diff
private func applySnapshot(animated: Bool = true) {
var snapshot = NSDiffableDataSourceSnapshot<HomeMoreSection, HomeMoreItem>()
snapshot.appendSections([.commonApps, .moreFunctions])
snapshot.appendItems(
commonItems.map { HomeMoreItem.menu(uri: $0.uri) },
toSection: .commonApps
)
snapshot.appendItems(
moreItems.map { HomeMoreItem.menu(uri: $0.uri) },
toSection: .moreFunctions
)
dataSource.apply(snapshot, animatingDifferences: animated)
}
///
private func rebuildMenus() {
let services = appServices
viewModel.buildMenus(
@ -51,7 +162,7 @@ final class HomeMoreFunctionsViewController: UIViewController {
currentRoleId: services.permissionContext.currentRole?.id
)
commonURIs = commonMenuStore.load(menuItems: viewModel.menuItems)
tableView.reloadData()
applySnapshot()
}
private var commonItems: [HomeMenuItem] {
@ -62,6 +173,7 @@ final class HomeMoreFunctionsViewController: UIViewController {
viewModel.menuItems.filter { !isCommonURI($0.uri) }
}
/// URI
private func menuItem(for uri: String) -> HomeMenuItem? {
let availableURIs = Set(viewModel.menuItems.map(\.uri))
let resolvedUri = HomeMenuRouter.canonicalURI(for: uri, availableURIs: availableURIs)
@ -73,20 +185,23 @@ final class HomeMoreFunctionsViewController: UIViewController {
)
}
/// URI
private func isCommonURI(_ uri: String) -> Bool {
let aliasKey = HomeMenuRouter.menuAliasKey(for: uri)
return commonURIs.contains { HomeMenuRouter.menuAliasKey(for: $0) == aliasKey }
}
///
private func toggleCommon(_ item: HomeMenuItem, isCommon: Bool) {
if isCommon {
commonURIs = commonMenuStore.remove(item.uri, current: commonURIs)
} else {
commonURIs = commonMenuStore.add(item.uri, current: commonURIs, menuItems: viewModel.menuItems)
}
tableView.reloadData()
applySnapshot()
}
///
private func openMenu(_ item: HomeMenuItem) {
let route = HomeMenuRouter.resolve(uri: item.uri, title: item.title)
if case .destination(let homeRoute) = route, homeRoute == .moreFunctions { return }
@ -94,108 +209,24 @@ final class HomeMoreFunctionsViewController: UIViewController {
}
}
extension HomeMoreFunctionsViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int { 2 }
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 1 }
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
section == 0 ? "常用应用" : "更多功能"
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: HomeMoreMenuGridCell.reuseID, for: indexPath) as! HomeMoreMenuGridCell
let isCommon = indexPath.section == 0
let items = isCommon ? commonItems : moreItems
cell.configure(items: items, isCommon: isCommon) { [weak self] item in
self?.openMenu(item)
} onToggle: { [weak self] item in
self?.toggleCommon(item, isCommon: isCommon)
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let count = indexPath.section == 0 ? commonItems.count : moreItems.count
let rows = max(1, Int(ceil(Double(count) / 3.0)))
return CGFloat(rows) * 124 + 8
}
}
private final class HomeMoreMenuGridCell: UITableViewCell {
static let reuseID = "HomeMoreMenuGridCell"
private var items: [HomeMenuItem] = []
private var isCommon = false
private var onSelect: ((HomeMenuItem) -> Void)?
private var onToggle: ((HomeMenuItem) -> Void)?
private lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumInteritemSpacing = 14
layout.minimumLineSpacing = AppMetrics.Spacing.mediumLarge
let view = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.backgroundColor = .clear
view.isScrollEnabled = false
view.dataSource = self
view.delegate = self
view.register(HomeMoreMenuItemCell.self, forCellWithReuseIdentifier: HomeMoreMenuItemCell.reuseID)
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(AppMetrics.Spacing.mediumLarge)
}
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(
items: [HomeMenuItem],
isCommon: Bool,
onSelect: @escaping (HomeMenuItem) -> Void,
onToggle: @escaping (HomeMenuItem) -> Void
) {
self.items = items
self.isCommon = isCommon
self.onSelect = onSelect
self.onToggle = onToggle
collectionView.reloadData()
}
}
extension HomeMoreMenuGridCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
items.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeMoreMenuItemCell.reuseID, for: indexPath) as! HomeMoreMenuItemCell
let item = items[indexPath.item]
cell.configure(item: item, isCommon: isCommon) { [weak self] in
self?.onToggle?(item)
}
return cell
}
// MARK: - UICollectionViewDelegate
extension HomeMoreFunctionsViewController: UICollectionViewDelegate {
/// Cell
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
onSelect?(items[indexPath.item])
}
guard let item = dataSource.itemIdentifier(for: indexPath),
case .menu(let uri) = item
else { return }
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (collectionView.bounds.width - 28) / 3
return CGSize(width: width, height: 112)
let allItems = commonItems + moreItems
guard let menuItem = allItems.first(where: { $0.uri == uri }) else { return }
openMenu(menuItem)
}
}
// MARK: - Menu Item Cell
/// Cell
private final class HomeMoreMenuItemCell: UICollectionViewCell {
static let reuseID = "HomeMoreMenuItemCell"
@ -204,6 +235,7 @@ private final class HomeMoreMenuItemCell: UICollectionViewCell {
private let toggleButton = UIButton(type: .system)
private var onToggle: (() -> Void)?
///
override init(frame: CGRect) {
super.init(frame: frame)
contentView.backgroundColor = .white
@ -243,6 +275,7 @@ private final class HomeMoreMenuItemCell: UICollectionViewCell {
fatalError("init(coder:) has not been implemented")
}
///
func configure(item: HomeMenuItem, isCommon: Bool, onToggle: @escaping () -> Void) {
self.onToggle = onToggle
titleLabel.text = item.title
@ -254,7 +287,15 @@ private final class HomeMoreMenuItemCell: UICollectionViewCell {
toggleButton.tintColor = isCommon ? UIColor(hex: 0xFF1111) : AppDesignUIKit.primary
}
///
@objc private func toggleTapped() {
onToggle?()
}
}
/// section
private extension Array {
subscript(safe index: Int) -> Element? {
indices.contains(index) ? self[index] : nil
}
}