Files
suixinkan_ios_uikit/suixinkan_ios/Features/Home/ViewControllers/HomeMoreFunctionsViewController.swift
汉秋 d99a5b1bf8 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>
2026-06-26 15:16:12 +08:00

302 lines
12 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}
}