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>
302 lines
12 KiB
Swift
302 lines
12 KiB
Swift
//
|
||
// HomeMoreFunctionsViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
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 {
|
||
|
||
private let viewModel = HomeViewModel()
|
||
private let commonMenuStore = HomeCommonMenuStore()
|
||
private var commonURIs: [String] = []
|
||
|
||
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
|
||
|
||
configureDataSource()
|
||
view.addSubview(collectionView)
|
||
collectionView.snp.makeConstraints { make in
|
||
make.edges.equalToSuperview()
|
||
}
|
||
|
||
viewModel.onChange = { [weak self] in
|
||
self?.applySnapshot()
|
||
}
|
||
rebuildMenus()
|
||
|
||
appServices.permissionContext.onChange = { [weak self] in
|
||
self?.rebuildMenus()
|
||
}
|
||
}
|
||
|
||
/// 构建 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(
|
||
from: services.permissionContext.rolePermissions,
|
||
currentRoleId: services.permissionContext.currentRole?.id
|
||
)
|
||
commonURIs = commonMenuStore.load(menuItems: viewModel.menuItems)
|
||
applySnapshot()
|
||
}
|
||
|
||
private var commonItems: [HomeMenuItem] {
|
||
commonURIs.compactMap { menuItem(for: $0) }
|
||
}
|
||
|
||
private var moreItems: [HomeMenuItem] {
|
||
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)
|
||
guard let existing = viewModel.menuItems.first(where: { $0.uri == resolvedUri }) else { return nil }
|
||
return HomeMenuItem(
|
||
title: HomeMenuRouter.displayTitle(for: resolvedUri, fallback: existing.title),
|
||
uri: resolvedUri,
|
||
iconSrc: existing.iconSrc
|
||
)
|
||
}
|
||
|
||
/// 判断 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)
|
||
}
|
||
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 }
|
||
HomeMenuRouting.openRoute(route, from: self)
|
||
}
|
||
}
|
||
|
||
// MARK: - UICollectionViewDelegate
|
||
|
||
extension HomeMoreFunctionsViewController: UICollectionViewDelegate {
|
||
/// 点击网格项打开对应功能页(加减按钮由 Cell 内部处理)。
|
||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||
guard let item = dataSource.itemIdentifier(for: indexPath),
|
||
case .menu(let uri) = item
|
||
else { return }
|
||
|
||
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"
|
||
|
||
private let iconView = UIImageView()
|
||
private let titleLabel = UILabel()
|
||
private let toggleButton = UIButton(type: .system)
|
||
private var onToggle: (() -> Void)?
|
||
|
||
/// 初始化实例。
|
||
override init(frame: CGRect) {
|
||
super.init(frame: frame)
|
||
contentView.backgroundColor = .white
|
||
contentView.layer.cornerRadius = 8
|
||
|
||
titleLabel.font = .systemFont(ofSize: AppMetrics.FontSize.callout)
|
||
titleLabel.textColor = UIColor(hex: 0x252525)
|
||
titleLabel.textAlignment = .center
|
||
titleLabel.numberOfLines = 1
|
||
titleLabel.adjustsFontSizeToFitWidth = true
|
||
|
||
toggleButton.addTarget(self, action: #selector(toggleTapped), for: .touchUpInside)
|
||
contentView.addSubview(toggleButton)
|
||
|
||
let stack = UIStackView(arrangedSubviews: [iconView, titleLabel])
|
||
stack.axis = .vertical
|
||
stack.spacing = AppMetrics.Spacing.mediumLarge + 1
|
||
stack.alignment = .center
|
||
contentView.addSubview(stack)
|
||
|
||
stack.snp.makeConstraints { make in
|
||
make.center.equalToSuperview()
|
||
make.leading.trailing.equalToSuperview().inset(4)
|
||
}
|
||
iconView.snp.makeConstraints { make in
|
||
make.width.height.equalTo(34)
|
||
}
|
||
toggleButton.snp.makeConstraints { make in
|
||
make.top.trailing.equalToSuperview().inset(2)
|
||
make.width.height.equalTo(28)
|
||
}
|
||
iconView.contentMode = .scaleAspectFit
|
||
}
|
||
|
||
@available(*, unavailable)
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
|
||
/// 配置菜单项展示内容与常用应用切换按钮。
|
||
func configure(item: HomeMenuItem, isCommon: Bool, onToggle: @escaping () -> Void) {
|
||
self.onToggle = onToggle
|
||
titleLabel.text = item.title
|
||
let symbol = UIImage(systemName: HomeIconCatalog.iconName(for: item.uri))
|
||
iconView.loadRemoteImage(urlString: item.iconSrc, contentMode: .scaleAspectFit, placeholder: symbol)
|
||
iconView.tintColor = AppDesign.primary
|
||
let iconName = isCommon ? "minus.circle.fill" : "plus.circle.fill"
|
||
toggleButton.setImage(UIImage(systemName: iconName), for: .normal)
|
||
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
|
||
}
|
||
}
|